A security-focused REST API built with Spring Boot that demonstrates enterprise-grade authentication and authorization patterns. This application implements a defense-in-depth security model combining mutual TLS (mTLS), JSON Web Tokens (JWT), and Role-Based Access Control (RBAC).
Secure Notes API is a note-taking service that allows users to create, read, update, and delete personal notes. The application prioritizes security at every layer, making it an excellent reference for implementing secure APIs in enterprise environments.
- Mutual TLS (mTLS): Transport-layer security requiring both client and server certificate authentication
- JWT Authentication: Stateless application-layer authentication with signed tokens
- Role-Based Access Control: Fine-grained authorization with USER and ADMIN roles
- Audit Logging: Comprehensive logging of all security-relevant events
- Input Validation: Request validation using Bean Validation annotations
The application implements a three-layer security model:
┌─────────────────────────────────────────────────────────────┐
│ Client Request │
└─────────────────────────────┬───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: mTLS │
│ │
│ • Server presents certificate to client │
│ • Client must present valid certificate to server │
│ • Connection rejected at TLS handshake if invalid │
│ • Provides transport encryption and client identity │
└─────────────────────────────┬───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: JWT Authentication │
│ │
│ • Client sends JWT in Authorization header │
│ • Server validates signature, expiration, and claims │
│ • Extracts user identity and roles from token │
│ • Rejects requests with invalid or expired tokens │
└─────────────────────────────┬───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: RBAC Authorization │
│ │
│ • Checks user role against endpoint requirements │
│ • Enforces resource ownership rules │
│ • ADMIN role has elevated privileges │
│ • USER role restricted to own resources │
└─────────────────────────────┬───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Application Logic │
│ │
│ • Process request │
│ • Log audit event │
│ • Return response │
└─────────────────────────────────────────────────────────────┘
Traditional TLS only authenticates the server to the client. Mutual TLS extends this by also requiring the client to present a valid certificate, providing two-way authentication at the transport layer.
Implementation Details:
- Server certificate stored in PKCS12 keystore (
keystore.p12) - Trusted client certificates stored in truststore (
truststore.p12) - Configured via
application.ymlwithclient-auth: need - Connections without valid client certificates are rejected during TLS handshake
Benefits:
- Prevents unauthorized network access before reaching application code
- Provides cryptographic proof of client identity
- Enables zero-trust network architecture
JSON Web Tokens provide stateless authentication at the application layer. After successful login, clients receive a signed token containing their identity and roles.
Token Structure:
{
"sub": "alice",
"roles": "USER",
"iat": 1702312800,
"exp": 1702399200
}Implementation Details:
- Tokens signed using HMAC-SHA384 algorithm
- Configurable secret key via environment variable
- 24-hour token expiration (configurable)
- Custom
JwtAuthenticationFilterextracts and validates tokens
Security Measures:
- Signature verification prevents token tampering
- Expiration checking prevents use of stale tokens
- No server-side session storage (stateless)
RBAC restricts system access based on user roles, implementing the principle of least privilege.
Roles:
| Role | Capabilities |
|---|---|
| USER | Create, read, update, delete own notes |
| ADMIN | All USER capabilities plus access to all notes, audit logs, and admin endpoints |
Access Rules:
| Endpoint | USER | ADMIN |
|---|---|---|
GET /notes |
Own notes only | Own notes only |
POST /notes |
✓ | ✓ |
GET /notes/{id} |
Own notes only | Any note |
PUT /notes/{id} |
Own notes only | Own notes only |
DELETE /notes/{id} |
Own notes only | Any note |
GET /admin/notes |
✗ | ✓ |
GET /admin/audit/logs |
✗ | ✓ |
All security-relevant events are logged to the database for compliance and forensic analysis.
Logged Events:
LOGIN_SUCCESS/LOGIN_FAILURE: Authentication attemptsCREATE_NOTE/UPDATE_NOTE/DELETE_NOTE: Data modificationsADMIN_ACCESS: Administrative endpoint access
Log Entry Structure:
| Field | Description |
|---|---|
| timestamp | When the event occurred |
| username | User who performed the action |
| action | Type of action performed |
| resourceType | Type of resource affected (e.g., NOTE) |
| resourceId | Identifier of affected resource |
| Component | Technology |
|---|---|
| Framework | Spring Boot 3.2.5 |
| Language | Java 17 |
| Build Tool | Gradle 8.7 |
| Database | PostgreSQL 16 |
| Security | Spring Security 6 |
| JWT Library | jjwt 0.12.5 |
| Validation | Jakarta Bean Validation |
| Testing | JUnit 5, Mockito, Spring Boot Test |
src/main/java/com/securenotes/
├── SecureNotesApplication.java # Application entry point
├── config/
│ ├── DataSeeder.java # Initial data population
│ └── SecurityConfig.java # Spring Security configuration
├── controller/
│ ├── AuthController.java # Authentication endpoints
│ ├── NoteController.java # Note CRUD endpoints
│ └── AdminController.java # Administrative endpoints
├── dto/
│ ├── AuthRequest.java # Login request payload
│ ├── AuthResponse.java # Login response with token
│ ├── CreateNoteRequest.java # Note creation payload
│ ├── UpdateNoteRequest.java # Note update payload
│ └── NoteResponse.java # Note response payload
├── entity/
│ ├── User.java # User entity
│ ├── Note.java # Note entity
│ └── AuditLogEntry.java # Audit log entity
├── repository/
│ ├── UserRepository.java # User data access
│ ├── NoteRepository.java # Note data access
│ └── AuditLogEntryRepository.java # Audit log data access
├── security/
│ ├── JwtUtil.java # JWT generation and validation
│ └── JwtAuthenticationFilter.java # Request filter for JWT processing
└── service/
├── AuthService.java # Authentication logic
├── NoteService.java # Note business logic
└── AuditService.java # Audit logging logic
POST /auth/login
Content-Type: application/json
{
"username": "alice",
"password": "password123"
}Response (200 OK):
{
"token": "eyJhbGciOiJIUzM4NCJ9..."
}All note endpoints require a valid JWT in the Authorization header:
Authorization: Bearer <token>GET /notesReturns all notes owned by the authenticated user.
POST /notes
Content-Type: application/json
{
"title": "My Note",
"content": "Note content here"
}GET /notes/{id}Returns the note if owned by user or user is ADMIN.
PUT /notes/{id}
Content-Type: application/json
{
"title": "Updated Title",
"content": "Updated content"
}Only the note owner can update.
DELETE /notes/{id}Owner or ADMIN can delete.
Require ADMIN role.
GET /admin/notesGET /admin/audit/logsGET /actuator/healthPublic endpoint for container orchestration.
- Docker and Docker Compose
- VS Code with Dev Containers extension
- Git
-
Clone the repository:
git clone <repository-url> cd secure-notes-api
-
Open in VS Code:
code . -
Start Dev Container:
- VS Code will prompt: "Reopen in Container"
- Click "Reopen in Container"
- Wait for the container to build
-
Verify the environment:
java --version # Java 17 gradle --version # Gradle 8.7
For mTLS to work, you need to generate certificates:
# Create certificates directory
mkdir -p certs
# Generate server keystore
keytool -genkeypair \
-alias securenotes \
-keyalg RSA \
-keysize 2048 \
-storetype PKCS12 \
-keystore certs/keystore.p12 \
-validity 365 \
-storepass changeit \
-dname "CN=localhost, OU=Dev, O=SecureNotes, L=City, ST=State, C=US"
# Generate client certificate
keytool -genkeypair \
-alias client \
-keyalg RSA \
-keysize 2048 \
-storetype PKCS12 \
-keystore certs/client.p12 \
-validity 365 \
-storepass changeit \
-dname "CN=client, OU=Dev, O=SecureNotes, L=City, ST=State, C=US"
# Export client certificate
keytool -exportcert \
-alias client \
-keystore certs/client.p12 \
-storepass changeit \
-file certs/client.crt
# Create truststore with client certificate
keytool -importcert \
-alias client \
-file certs/client.crt \
-keystore certs/truststore.p12 \
-storetype PKCS12 \
-storepass changeit \
-noprompt./gradlew bootRunThe application starts on https://localhost:8443.
The application seeds two test users on startup:
| Username | Password | Role |
|---|---|---|
| alice | password123 | USER |
| admin | adminpass | ADMIN |
# All tests
./gradlew test
# Unit tests only
./gradlew test --tests "com.securenotes.security.*"
./gradlew test --tests "com.securenotes.service.*"
# Integration tests only
./gradlew test --tests "com.securenotes.controller.*"| Category | Tests | Description |
|---|---|---|
| Unit - JwtUtil | 9 | Token generation, validation, claim extraction |
| Unit - JwtAuthenticationFilter | 5 | Request filtering, authentication setup |
| Unit - AuthService | 6 | Login logic, audit logging |
| Unit - NoteService | 8 | CRUD operations |
| Unit - AuditService | 3 | Audit log creation |
| Integration - AuthController | 5 | Login endpoint, validation |
| Integration - NoteController | 16 | CRUD, ownership, authorization |
| Integration - AdminController | 7 | Admin access, role enforcement |
| Total | 59 |
curl -k https://localhost:8443/actuator/health
# Result: SSL handshake error# Health check
curl -k --cert-type P12 --cert certs/client.p12:changeit \
https://localhost:8443/actuator/health
# Login
curl -k --cert-type P12 --cert certs/client.p12:changeit \
-X POST https://localhost:8443/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "password123"}'
# Save token
TOKEN="<token-from-login-response>"
# Create note
curl -k --cert-type P12 --cert certs/client.p12:changeit \
-X POST https://localhost:8443/notes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Test Note", "content": "Hello, World!"}'
# List notes
curl -k --cert-type P12 --cert certs/client.p12:changeit \
-H "Authorization: Bearer $TOKEN" \
https://localhost:8443/notes# Login as alice (USER role)
TOKEN_ALICE=$(curl -s -k --cert-type P12 --cert certs/client.p12:changeit \
-X POST https://localhost:8443/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "password123"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
# Login as admin (ADMIN role)
TOKEN_ADMIN=$(curl -s -k --cert-type P12 --cert certs/client.p12:changeit \
-X POST https://localhost:8443/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "adminpass"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
# Alice tries to access admin endpoint (should fail with 403)
curl -k --cert-type P12 --cert certs/client.p12:changeit \
-H "Authorization: Bearer $TOKEN_ALICE" \
https://localhost:8443/admin/notes
# Admin accesses admin endpoint (should succeed)
curl -k --cert-type P12 --cert certs/client.p12:changeit \
-H "Authorization: Bearer $TOKEN_ADMIN" \
https://localhost:8443/admin/notesA companion Python CLI tool is available in the py-cli/ directory for interacting with the API.
cd py-cli
python3 -m venv .venv
source .venv/bin/activate
pip install -e .# Login
securenotes login -u alice -p password123
# Create a note
securenotes notes create -t "My Note" -c "Content here"
# List notes
securenotes notes list
# Admin commands (requires ADMIN role)
securenotes login -u admin -p adminpass
securenotes admin notes
securenotes admin logs| Variable | Description | Default |
|---|---|---|
SPRING_DATASOURCE_URL |
Database JDBC URL | jdbc:postgresql://db:5432/securenotes |
SPRING_DATASOURCE_USERNAME |
Database username | postgres |
SPRING_DATASOURCE_PASSWORD |
Database password | postgres |
JWT_SECRET |
Secret key for signing JWTs | (development default) |
SSL_KEYSTORE |
Path to server keystore | certs/keystore.p12 |
SSL_KEYSTORE_PASSWORD |
Keystore password | changeit |
SSL_TRUSTSTORE |
Path to truststore | certs/truststore.p12 |
SSL_TRUSTSTORE_PASSWORD |
Truststore password | changeit |
- Use strong, unique passwords for all keystores
- Store secrets in a secrets manager (e.g., Azure Key Vault, HashiCorp Vault)
- Use certificates signed by a trusted CA
- Enable database connection encryption
- Configure appropriate token expiration times
- Implement rate limiting
- Set up monitoring and alerting
This project is for educational and portfolio purposes.
Built as a demonstration of enterprise security patterns in Spring Boot applications.