Security Architecture
Overview
StockEase implements a defense-in-depth security model with multiple layers of protection:
βββββββββββββββββββββββββββββββββββββββββββ
β HTTPS/TLS (Transport Security) β
βββββββββββββββββββββββββββββββββββββββββββ€
β CORS (Cross-Origin Resource Sharing) β
βββββββββββββββββββββββββββββββββββββββββββ€
β HTTP Security Headers (Security) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Authentication (JWT Tokens) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Authorization (Role-Based Access) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Input Validation & Sanitization β
βββββββββββββββββββββββββββββββββββββββββββ€
β Password Hashing (BCrypt) β
βββββββββββββββββββββββββββββββββββββββββββ€
β Database Security (Parameterized SQL) β
βββββββββββββββββββββββββββββββββββββββββββ
1. Transport Security (HTTPS/TLS)
Implementation
- Protocol: HTTPS only (TLS 1.2+)
- Certificate: Managed by Koyeb/CloudFlare
- Force HTTPS: All HTTP requests redirected to HTTPS
Configuration
# application.properties
server.ssl.enabled=true
server.ssl.key-store-type=PKCS12
# Certificates managed by infrastructureBenefits
- Encrypts all data in transit
- Prevents man-in-the-middle attacks
- Protects credentials and tokens
- Required for production APIs
2. Authentication & Authorization
JWT (JSON Web Tokens)
Purpose: Stateless, scalable authentication
Token Structure:
Header.Payload.Signature
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "user-id-uuid",
"username": "john.doe",
"role": "ADMIN",
"iat": 1701418200,
"exp": 1701504600,
"iss": "stockease-backend",
"aud": "stockease-frontend"
}
Signature:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)
Token Generation Flow:
1. User calls POST /api/auth/login
βββ Headers: Content-Type: application/json
βββ Body: { "username": "admin", "password": "admin123" }
2. AuthController receives request
βββ Calls AuthService.authenticate(username, password)
βββ AuthService validates credentials
3. Credential Validation
βββ Find user by username in database
βββ Compare provided password with BCrypt hash
βββ If match β proceed to token generation
βββ If no match β throw AuthenticationException (401)
βββ If user not found β throw AuthenticationException (401)
4. JWT Token Generation
βββ Create payload with:
β βββ User ID (sub claim)
β βββ Username
β βββ Role (ADMIN / USER)
β βββ Issue time (iat)
β βββ Expiration time (exp = iat + 24 hours)
β βββ Issuer (iss)
β βββ Audience (aud)
βββ Sign with secret key (HS256)
βββ Encode as base64url string
5. Response
βββ Status: 200 OK
βββ Body: { "token": "eyJhbG...", "expiresIn": 86400 }
βββ Client stores token (localStorage / sessionStorage)
### Login Sequence (simplified)
```mermaid
sequenceDiagram
participant U as User
participant FE as Frontend
participant BE as Backend (Spring Boot)
U->>FE: Submit credentials (username/password)
FE->>BE: POST /api/auth/login (JSON body)
BE->>BE: Authenticate (AuthenticationManager)
BE->>BE: Lookup user (UserRepository)
BE->>BE: Generate JWT (JwtUtil)
BE-->>FE: 200 OK + { token }
FE-->>U: Store token; use for subsequent requests
**Token Usage in Requests**:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9β¦
Every subsequent request must include this header
**Token Validation Flow**:
Request arrives at controller with JWT token
SecurityFilter intercepts request βββ Extracts token from Authorization header βββ Calls JwtProvider.validateToken(token) βββ Passes token to validation logic
JWT Validation βββ Verify token format (Header.Payload.Signature) βββ Verify signature using secret key βββ Check expiration time (exp claim vs current time) βββ Extract claims (user ID, role) βββ If valid β create Authentication object βββ If invalid/expired β throw JwtException (401) βββ If malformed β throw JwtException (401)
SecurityContext Set βββ Store Authentication in SecurityContext βββ Make available to controller via @AuthenticationPrincipal βββ Controller can access user info
Request Proceeds βββ SecurityFilter passes to next filter βββ Request reaches ProductController βββ Can access authenticated user info
### Role-Based Access Control (RBAC)
**Roles Defined**:
```java
public enum Role {
ADMIN, // Full access: create, read, update, delete
USER // Limited access: read only
}
Authorization Rules (implemented):
| Endpoint | Method | ADMIN | USER | Anonymous |
|---|---|---|---|---|
/api/health |
GET | β | β | β |
/api/auth/login |
POST | β | β | β (public) |
/api/products |
GET | β | β | β |
/api/products/paged |
GET | β | β | β |
/api/products/{id} |
GET | β | β | β |
/api/products |
POST | β | β | β |
/api/products/{id}/quantity |
PUT | β | β | β |
/api/products/{id}/price |
PUT | β | β | β |
/api/products/{id}/name |
PUT | β | β | β |
/api/products/low-stock |
GET | β | β | β |
/api/products/search |
GET | β | β | β |
/api/products/total-stock-value |
GET | β | β | β |
/api/products/{id} |
DELETE | β | β | β |
Notes: The table above reflects the current
SecurityConfigin code: login is permitted publicly (/api/auth/login), health is public, product create/delete are admin-only, and most read/update product endpoints allow ADMIN and USER.
API Map (quick reference)
| Endpoint | Purpose | Auth |
|---|---|---|
POST /api/auth/login |
Return JWT for valid credentials | Public (no token) |
GET /api/health |
Liveness/database connectivity | Public |
GET /api/products |
List products | JWT (ADMIN/USER) |
POST /api/products |
Create product | JWT (ADMIN) |
PUT /api/products/{id}/quantity |
Update product quantity | JWT (ADMIN/USER) |
DELETE /api/products/{id} |
Delete product | JWT (ADMIN) |
Authorization Implementation:
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ProductDTO> createProduct(
@RequestBody CreateProductRequest req) {
// Only users with ADMIN role can reach here
return productService.create(req);
}
// Or using method-level security:
@Service
public class ProductService {
@Secured("ROLE_ADMIN")
public void deleteProduct(UUID id) {
// Only ADMIN can call this method
}
}3. Password Security
BCrypt Hashing
Purpose: Store passwords securely, never in plaintext
BCrypt Properties: - One-way hash: Cannot reverse to get original password - Salt included: Each password has unique salt (prevents rainbow tables) - Adaptive: Slower algorithm resists brute force - Configurable strength: Cost factor 10-12 iterations
Password Hashing Flow:
1. User provides password: "MySecurePass123!"
2. BCryptPasswordEncoder
βββ Generate random salt
βββ Apply BCrypt algorithm with salt
βββ Produce hash: $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcg7b3XeKeUxWdeS86E36CHhzPm
βββ Store hash in database
3. Password Verification (login)
βββ User provides password: "MySecurePass123!"
βββ Retrieve stored hash from database
βββ Apply BCrypt with provided password and stored salt
βββ Compare computed hash with stored hash
βββ If match β password correct (401 vs 200)
βββ If no match β authentication failed (401 Unauthorized)
βββ Never store or log plaintext password
Configuration:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10); // strength 10
}
}4. API Security
CORS (Cross-Origin Resource Sharing)
Purpose: Control which origins can access the API
Configuration:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://stockease.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.exposedHeaders("X-Total-Count", "X-Page-Count")
.allowCredentials(true)
.maxAge(3600);
}
}Production Settings: - β
Specific
allowed origins (not *) - β
Limited HTTP
methods - β
Credentials allowed for same-site requests
- β
Limited max-age (3600 seconds)
Security Headers
Headers Set by Spring Security:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, must-revalidate
What They Prevent: -
HSTS: Forces HTTPS-only communication -
X-Content-Type-Options: nosniff: Prevents
MIME-type sniffing - X-Frame-Options: DENY:
Prevents clickjacking - X-XSS-Protection:
Enables browser XSS protection
5. Input Validation & Sanitization
Request Validation
Purpose: Prevent malformed data and malicious inputs
Validation Rules:
public class CreateProductRequest {
@NotNull(message = "Name cannot be null")
@NotBlank(message = "Name cannot be blank")
@Size(min = 3, max = 255, message = "Name must be 3-255 characters")
private String name;
@NotNull(message = "Price cannot be null")
@DecimalMin(value = "0.01", message = "Price must be > 0")
@DecimalMax(value = "999999.99", message = "Price cannot exceed 999999.99")
private BigDecimal price;
@NotNull(message = "Quantity cannot be null")
@Min(value = 0, message = "Quantity cannot be negative")
@Max(value = 1000000, message = "Quantity cannot exceed 1,000,000")
private Integer quantity;
@NotBlank(message = "SKU is required")
@Pattern(
regexp = "^[A-Z0-9-]{3,50}$",
message = "SKU must be 3-50 chars, alphanumeric and hyphens only"
)
private String sku;
}Validation Execution:
1. Request arrives at @PostMapping
2. @Valid annotation triggers validation
3. CreateProductRequest deserialized and validated
4. Validation fails β MethodArgumentNotValidException thrown
5. GlobalExceptionHandler catches exception
6. Returns 400 Bad Request with error details
Response (if validation fails):
{
"status": 400,
"error": "Validation Failed",
"message": "Validation failed for argument 'request'",
"details": [
{
"field": "name",
"message": "Name must be 3-255 characters"
},
{
"field": "price",
"message": "Price must be > 0"
}
]
}
SQL Injection Prevention
Parameterized Queries (Spring Data JPA):
// β
SAFE: Spring Data prevents SQL injection
productRepository.findBySku(userProvidedSku);
// β
SAFE: Native query with parameters
@Query("SELECT p FROM Product p WHERE p.sku = ?1")
Optional<Product> findBySku(String sku);
// β DANGEROUS: String concatenation
Query q = entityManager.createNativeQuery(
"SELECT * FROM products WHERE sku = '" + userInput + "'"
);
// This allows SQL injection!6. Database Security
Connection Security
# application.properties
spring.datasource.url=jdbc:postgresql://host:5432/stockease?sslmode=require
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}Environment Variables (never
hardcoded): - DB_USER: Database username -
DB_PASSWORD: Database password -
JWT_SECRET: JWT signing key
Row-Level Security (Future)
-- Example: Users can only see products they created
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_products ON products
USING (created_by = current_user_id());7. Audit Logging
Logged Events:
- User login attempt (success/failure)
- User registration
- Product creation (who, when)
- Product modification (who, when)
- Product deletion (who, when)
- Authorization failures (403 errors)
- API errors (500 errors)
Log Format:
[2025-10-31 10:30:45] INFO [AuthService] User 'john.doe' logged in successfully
[2025-10-31 10:31:12] INFO [ProductService] Product 'Widget' created by admin (ID: ...)
[2025-10-31 10:32:00] WARN [SecurityFilter] Unauthorized access attempt: invalid token
[2025-10-31 10:33:15] ERROR [ProductService] Database error: connection timeout
8. Security Best Practices Implemented
| Practice | Implementation | Status |
|---|---|---|
| HTTPS/TLS | Koyeb managed certificates | β |
| JWT Tokens | HS256 signing with secret key | β |
| Password Hashing | BCrypt with cost factor 10 | β |
| RBAC | ADMIN/USER roles | β |
| Input Validation | @Valid + JSR-303 annotations | β |
| SQL Injection Prevention | Parameterized queries (JPA) | β |
| CORS | Restricted to specific origins | β |
| Security Headers | HSTS, X-Frame-Options, etc. | β |
| Audit Logging | Request logging in critical paths | β |
| Secrets Management | Environment variables, no hardcoding | β |
| Rate Limiting | Per-endpoint rate limiting (future) | β³ |
| API Key Management | Support for API keys (future) | β³ |
9. Default Credentials (Development/Testing)
Purpose: Enable quick testing without additional setup
Admin User:
Username: admin
Password: admin123
Role: ADMIN
Regular User:
Username: user
Password: user123
Role: USER
β οΈ WARNING: These credentials are seeded via V3 migration for development only. In production, create secure passwords and remove seed data.
10. Security Testing
Test Coverage
- Authentication tests (valid/invalid credentials)
- Authorization tests (ADMIN vs USER endpoints)
- Password hashing verification
- JWT token validation/expiration
- CORS policy enforcement
- Input validation edge cases
- SQL injection attempts (should fail)
Example Security Test
@Test
public void testUnauthorizedAccessToProductCreation() {
// User role cannot create products
mockMvc.perform(post("/api/products")
.header("Authorization", "Bearer " + userToken)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonRequest))
.andExpect(status().isForbidden()); // 403
}
@Test
public void testInvalidTokenRejection() {
mockMvc.perform(get("/api/products")
.header("Authorization", "Bearer invalid-token"))
.andExpect(status().isUnauthorized()); // 401
}11. Deployment Security Checklist
Before production deployment: - [ ] Change all default credentials - [ ] Remove seed data (V3 migration) - [ ] Set strong JWT secret (32+ characters) - [ ] Enable HTTPS/TLS - [ ] Configure CORS for production domain only - [ ] Set up audit logging - [ ] Enable database backups - [ ] Review security headers - [ ] Perform security testing - [ ] Set up monitoring/alerting - [ ] Document security policies - [ ] Conduct code review for security issues
Related Documentation
Main Architecture Topics
- Architecture Overview - Overall system context and security decisions
- Backend Architecture - Spring Security configuration in code
- Service Layers - Authorization checks in Controller and Service layers
- Deployment Architecture - Infrastructure security and TLS/HTTPS setup
Architecture Decisions (ADRs)
- Database Choice - Database-level security implications
- Validation Strategy - Input validation as first line of defense
Design Patterns & Practices
- Security Patterns - JWT token generation, BCrypt hashing, CORS
- Repository Pattern - Query-level security considerations
Infrastructure & Deployment
- CI/CD Pipeline - Secret management in GitHub Actions
- Staging Configuration - Security testing environment
Document Version: 1.0
Last Updated: October 31, 2025
Status: Production Ready