Skip to main content

Feature 1: Security Hardening

Purpose: Fix critical security vulnerabilities identified in code analysis.

Priority: CRITICAL (fixes production blockers) Estimated Time: 4-6 hours Dependencies: None Status: ✅ COMPLETE (2026-01-04)


Overview

This feature fixes 7 critical security vulnerabilities that prevent production deployment:

  1. CORS wildcard configuration
  2. Missing JWT token validation
  3. CSRF disabled globally
  4. No password validation
  5. Missing rate limiting
  6. Hardcoded secrets
  7. Missing input validation

Bundled Enhancements (to avoid revisiting files):

  • Centralized exception handling
  • Optional.get() safety fixes
  • Transaction annotation fixes
  • Production infrastructure (Actuator, Hikari, logging)
  • Unit tests for all new code

Prerequisites

Before starting:

  1. Clean branch: Start from main/develop
  2. Build passing: ./gradlew build succeeds
  3. Database running: PostgreSQL available
  4. Environment file: .env configured from .env.example

Step 1: CORS Configuration

Files:

  • server/src/main/java/com/saas/springular/common/security/WebSecurityConfig.java
  • server/src/main/resources/application.yml

1.1 Update CORS Method

Replace the corsConfigurationSource() method to:

  • Use allowedOriginPatterns instead of wildcard (*)
  • Enable allowCredentials(true) for cookies/auth
  • Add CSRF headers to allowedHeaders: X-XSRF-TOKEN, X-CSRF-TOKEN
  • Expose rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • Set maxAge(3600L) to cache preflight for 1 hour

1.2 Add Origins to application.yml

Add auth.authorized-redirect-origins list with localhost:4200, localhost:8081, and ${FRONTEND_CLIENT_HOST}.


Step 2: Enable CSRF Protection

Files:

  • server/src/main/java/com/saas/springular/common/security/WebSecurityConfig.java
  • client/src/app/core/interceptor/auth.interceptor.ts

2.1 Enable CSRF in Security Config

Replace .csrf(AbstractHttpConfigurer::disable) with:

  • CookieCsrfTokenRepository.withHttpOnlyFalse() for cookie-based tokens
  • ignoringRequestMatchers("/auth/**", "/webhook", "/webhook/**") to exempt auth and webhooks

Note: /auth/** must be excluded because login/register requests don't have a CSRF token yet.

2.2 Add CSRF Header to Frontend Interceptor

Add helper functions to:

  • Get CSRF token from cookies (XSRF-TOKEN)
  • Check if request method requires CSRF (POST, PUT, DELETE, PATCH)
  • Add X-XSRF-TOKEN header for state-changing requests

Step 3: JWT Token Validation

Files:

  • server/src/main/java/com/saas/springular/common/security/JwtTokenProvider.java
  • server/src/main/java/com/saas/springular/common/security/JwtTokenAuthenticationFilter.java

3.1 Add Validation Method to JwtTokenProvider

Add validateToken(String token) method that catches and logs:

  • ExpiredJwtException
  • UnsupportedJwtException
  • MalformedJwtException
  • SignatureException
  • IllegalArgumentException

3.2 Use Validation in JwtTokenAuthenticationFilter

Update doFilterInternal to use jwtTokenProvider.validateToken(jwt) instead of duplicate validation logic.


Step 4: Password Validation

New Files:

  • server/src/main/java/com/saas/springular/common/validation/ValidPassword.java
  • server/src/main/java/com/saas/springular/common/validation/PasswordValidator.java

4.1 Create ValidPassword Annotation

Custom constraint annotation with PasswordValidator as validatedBy.

4.2 Create PasswordValidator

Implementation: Minimum 12 characters, any characters allowed (NIST-aligned).

4.3 Apply to SignupRequest

Add @ValidPassword annotation. Use @Size(max = 128) for upper limit only.

4.4 Add @Email to LoginRequest

Add @Email annotation for consistency.


Step 5: Rate Limiting

New Files:

  • server/src/main/java/com/saas/springular/common/security/RateLimitService.java
  • server/src/main/java/com/saas/springular/common/security/RateLimitProperties.java
  • server/src/main/java/com/saas/springular/common/security/RateLimitHeaders.java
  • server/src/main/java/com/saas/springular/common/exception/TooManyRequestsException.java

5.1 Add Bucket4j Dependency

Add com.bucket4j:bucket4j-core:8.10.1 to build.gradle.

5.2 Create RateLimitProperties

Configuration class with capacity, refillTokens, refillDurationSeconds.

5.3 Create RateLimitService

Token bucket implementation using Bucket4j with ConcurrentHashMap cache.

5.4 Create RateLimitHeaders

Constants for header names: LIMIT, REMAINING, RESET.

5.5 Create TooManyRequestsException

Exception with @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS).

5.6 Add Rate Limit Config to application.yml

Add rate-limit.* properties with defaults via environment variables.

5.7 Apply Rate Limiting to AuthenticationController

Add rate limiting to /login and /register endpoints. Include rate limit headers in responses.


Step 6: Centralized Exception Handling

New Files:

  • server/src/main/java/com/saas/springular/common/exception/ResourceNotFoundException.java
  • server/src/main/java/com/saas/springular/common/exception/ErrorResponse.java
  • server/src/main/java/com/saas/springular/common/exception/GlobalExceptionHandler.java

6.1 Create ResourceNotFoundException

Exception with constructors for simple message and resource/field/value format.

6.2 Create ErrorResponse

Record with message, status, path, timestamp.

6.3 Create GlobalExceptionHandler

@RestControllerAdvice with handlers for:

  • ResourceNotFoundException → 404
  • TooManyRequestsException → 429
  • BadCredentialsException → 401
  • MethodArgumentNotValidException → 400
  • IllegalArgumentException → 400
  • Exception → 500

Step 7: Fix Optional.get() and Transactions

File: server/src/main/java/com/saas/springular/user/api/AuthenticationServiceImpl.java

7.1 Fix Unsafe Optional.get()

Replace .get() with .orElseThrow(() -> new ResourceNotFoundException(...)).

7.2 Fix Transaction Annotations

  • loginUser(): @Transactional (creates refresh token)
  • refresh(): @Transactional (creates new refresh token, may delete expired)
  • registerUser(): @Transactional (writes user)

7.3 Use @RequiredArgsConstructor

Change @AllArgsConstructor to @RequiredArgsConstructor for consistency.


Step 8: Production Infrastructure

8.1 Add Dependencies (build.gradle)

  • com.bucket4j:bucket4j-core:8.10.1 - Rate limiting
  • org.springframework.boot:spring-boot-starter-actuator - Monitoring
  • com.fasterxml.jackson.datatype:jackson-datatype-jsr310 - Date/time serialization
  • Checkstyle plugin configuration

8.2 Update application.yml

  • Hikari connection pool settings
  • Actuator endpoints (health, info, metrics)
  • Secure logging (hide SQL parameters)

8.3 Create application-dev.yml

Dev profile with devtools enabled and ddl-auto: update.


Step 9: Unit Tests

New Files:

  • server/src/test/java/com/saas/springular/common/security/RateLimitServiceTest.java
  • server/src/test/java/com/saas/springular/common/validation/PasswordValidatorTest.java
  • server/src/test/java/com/saas/springular/common/exception/GlobalExceptionHandlerTest.java

Tests cover:

  • Rate limit bucket behavior and limits
  • Password length validation
  • Exception handler responses

Step 10: Fix Frontend API URL

File: client/src/environments/environment.prod.ts

Change apiUrl from http://localhost:4200 to '' (empty string) so nginx can proxy /api requests correctly.


Verification Checklist

Run all verifications before merging:

  1. ✅ Build passes: ./gradlew clean build
  2. ✅ Tests pass: ./gradlew test
  3. ✅ Checkstyle passes: ./gradlew checkstyleMain checkstyleTest
  4. ✅ Frontend builds: cd client && npm run build
  5. ✅ Registration works with valid password
  6. ✅ Rate limiting prevents brute force (11th request fails)
  7. ✅ Weak passwords rejected

Summary

Security Issues Fixed:

  • ✅ CORS wildcard → allowedOriginPatterns with credentials
  • ✅ No JWT validation → Full validation with error handling
  • ✅ CSRF disabled → Cookie-based CSRF tokens with frontend integration
  • ✅ No password policy → 12+ chars minimum
  • ✅ No rate limiting → Configurable rate limiting on auth endpoints
  • ✅ Hardcoded secrets → Environment variables with defaults
  • ✅ No input validation → @Valid on all DTOs with custom validators

Bundled Enhancements:

  • ✅ Centralized exception handling with consistent responses
  • ✅ Optional.get() → orElseThrow for safety
  • ✅ Transaction annotations fixed for write operations
  • ✅ Production infrastructure (Actuator, Hikari, logging)
  • ✅ 19 unit tests for all new code

Next: Feature 2: Reliability Improvements