Skip to main content

Overview

InvestGo implements a robust security architecture using Spring Security with JWT-based authentication. The system provides role-based access control (RBAC), password encryption, and stateless session management.

Security Architecture

The security configuration is implemented in MySecurityConfig.java:23 and includes:
  • JWT-based stateless authentication
  • BCrypt password encryption
  • Role-based authorization (ADMIN & INVERSIONISTA)
  • CORS and CSRF configuration
  • Custom authentication entry point
  • Security filter chain

Spring Security Configuration

Core Components

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;
    
    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;
    
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Security Features

FeatureConfigurationDescription
AuthenticationJWT-basedStateless token authentication
Password HashingBCryptIndustry-standard password encryption
Session ManagementSTATELESSNo server-side sessions
CSRF ProtectionDisabledNot needed for stateless JWT
CORSDisabled (Controller-level)Handled via @CrossOrigin annotations
Method SecurityEnabled@PreAuthorize annotations supported

Role-Based Access Control

User Roles

InvestGo defines two primary roles:
Capabilities:
  • Full system access
  • User management
  • Factoring configuration
  • Risk level management
  • Bank and currency management
  • View all transactions and investments
Default Admin Account (seeded on first run):
  • Username: jamie
  • Password: Admin12345 (BCrypt encrypted)
  • Email: [email protected]
  • Initial wallet balance: S/. 10,000,000
Capabilities:
  • View available invoices
  • Make investments
  • Manage personal portfolio
  • Wallet operations (deposit/withdrawal)
  • View own transactions
  • Profile management
Access Level: Limited to own resources and public endpoints

Implementing Role-Based Access

Use @PreAuthorize annotations to restrict endpoints by role:
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/usuarios")
public ResponseEntity<?> getAllUsers() {
    // Only ADMIN can access
    return ResponseEntity.ok(usuarioService.listarUsuarios());
}

Protected vs Public Endpoints

Public Endpoints (No Authentication Required)

From MySecurityConfig.java:59:
.antMatchers("/generate-token", "/api/**").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
Accessible without JWT token:
Endpoint PatternDescription
POST /generate-tokenUser authentication and token generation
/api/**All endpoints under /api/ path
OPTIONS *All OPTIONS requests (for CORS preflight)
The /api/** pattern appears to be intended for public API endpoints or user registration. Verify actual implementation for specific use cases.

Protected Endpoints (Authentication Required)

All other endpoints require valid JWT token:
.anyRequest().authenticated()
Examples of protected endpoints:
  • GET /actual-usuario - Get current user information
  • /facturas/** - Invoice management
  • /inversiones/** - Investment operations
  • /usuarios/** - User management
  • /transacciones/** - Transaction history
  • /cartera/** - Wallet operations

Password Encryption

BCrypt Configuration

InvestGo uses BCrypt for password hashing (MySecurityConfig.java:41-43):
@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Password Hashing in Practice

From the initial admin user creation (SistemaFactoringBackendApplication.java:74):
usuario.setPassword(bCryptPasswordEncoder.encode("Admin12345"));
BCrypt Characteristics:
  • Adaptive hashing function
  • Automatic salt generation
  • Configurable work factor (default: 10)
  • One-way encryption (cannot be decrypted)
Password Security Best Practices:
  • Never store plain-text passwords
  • Always use passwordEncoder.encode() when saving passwords
  • Validate password strength on registration
  • Implement password complexity requirements
  • Consider password rotation policies for admin accounts

Example: User Registration with Password Encryption

@PostMapping("/api/usuarios/register")
public ResponseEntity<?> registerUser(@RequestBody Usuario usuario) {
    // Hash password before saving
    usuario.setPassword(passwordEncoder.encode(usuario.getPassword()));
    
    Usuario savedUser = usuarioService.insertaActualizaUsuario(usuario);
    return ResponseEntity.ok(savedUser);
}

Security Filter Chain

Filter Execution Order

1

JwtAuthenticationFilter

Executes before UsernamePasswordAuthenticationFilter to extract and validate JWT tokens.
2

Token Extraction

Reads Authorization header and extracts token (format: Bearer <token>).
3

Token Validation

Validates token signature, expiration, and extracts username.
4

User Loading

Loads UserDetails from database using extracted username.
5

Authentication Set

Creates and sets authentication in SecurityContext.
6

Filter Chain Continues

Request proceeds to controller if authenticated, or rejected by entry point.

Custom Authentication Entry Point

When authentication fails, JwtAuthenticationEntryPoint.java:19 handles the response:
@Override
public void commence(HttpServletRequest request, 
                     HttpServletResponse response,
                     AuthenticationException authException) 
                     throws IOException, ServletException {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
                      "USUARIO NO AUTORIZADO");
}
Behavior:
  • Returns HTTP 401 (Unauthorized)
  • Response body: "USUARIO NO AUTORIZADO"
  • Triggered when no valid authentication is present

CORS Configuration

Controller-Level CORS

While CORS is disabled in security config (MySecurityConfig.java:56-57), it’s enabled at the controller level:
@RestController
@CrossOrigin("*")
public class AuthenticationController {
    // Allows all origins
}
Production Recommendation: Replace @CrossOrigin("*") with specific allowed origins:
@CrossOrigin(origins = "https://yourdomain.com")
Allowing all origins (*) is a security risk in production environments.
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        // ... rest of configuration
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("https://yourdomain.com"));
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
    configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    configuration.setAllowCredentials(true);
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

Session Management

Stateless Sessions

From MySecurityConfig.java:65:
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Implications:
  • No HTTP sessions created or used
  • No cookies for session tracking
  • Each request must include JWT token
  • Horizontal scaling is easier (no session replication needed)
  • No server-side state for authentication
Stateless architecture makes InvestGo highly scalable. Each request is independently authenticated without server-side session storage.

Security Testing

Testing Authentication

curl -X POST http://localhost:8091/generate-token \
  -H "Content-Type: application/json" \
  -d '{
    "username": "jamie",
    "password": "Admin12345"
  }'

Testing Role-Based Access

# Login as ADMIN
ADMIN_TOKEN=$(curl -s -X POST http://localhost:8091/generate-token \
  -H "Content-Type: application/json" \
  -d '{"username": "jamie", "password": "Admin12345"}' \
  | jq -r '.token')

# Access admin endpoint
curl -X GET http://localhost:8091/usuarios \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# Expected: 200 OK (if endpoint has @PreAuthorize("hasRole('ADMIN')"))

# Login as INVERSIONISTA
INVERSOR_TOKEN=$(curl -s -X POST http://localhost:8091/generate-token \
  -H "Content-Type: application/json" \
  -d '{"username": "investor1", "password": "password"}' \
  | jq -r '.token')

# Try to access admin endpoint
curl -X GET http://localhost:8091/usuarios \
  -H "Authorization: Bearer $INVERSOR_TOKEN"
# Expected: 403 Forbidden (if role check is implemented)

Common Security Issues

Symptoms: Always receiving 401 Unauthorized even with valid tokenPossible Causes:
  • Missing “Bearer ” prefix in Authorization header
  • Token copied with extra whitespace or line breaks
  • Token expired (check exp claim)
  • Secret key mismatch between token generation and validation
Solution: Verify header format is exactly Authorization: Bearer <token>
Symptoms: Browser console shows CORS policy errorsPossible Causes:
  • Controller missing @CrossOrigin annotation
  • OPTIONS requests not properly configured
  • Credentials mode mismatch
Solution: Add @CrossOrigin to controllers or configure global CORS
Symptoms: Login fails with “Credenciales ivalidas” for correct passwordPossible Causes:
  • Password not BCrypt encoded in database
  • BCrypt encoder not configured correctly
  • Password encoding done multiple times
Solution: Ensure password is encoded exactly once before saving
Symptoms: 403 Forbidden despite having valid tokenPossible Causes:
  • User role doesn’t match @PreAuthorize requirement
  • Role name format mismatch (e.g., “ADMIN” vs “ROLE_ADMIN”)
  • User account disabled
Solution: Check user’s idTipoUsu field matches required role ID

Security Best Practices

Critical Security Considerations:
  1. Change Default Secret Key: The current JWT secret (examportal) should be changed to a strong, unique value in production
  2. Use Environment Variables: Never hardcode secrets in source code
    @Value("${jwt.secret}")
    private String SECRET_KEY;
    
  3. Enable HTTPS: Always use TLS/SSL in production to prevent token interception
  4. Implement Rate Limiting: Protect /generate-token endpoint from brute force attacks
  5. Validate Input: Sanitize all user input to prevent injection attacks
  6. Audit Logging: Log authentication attempts and security events
  7. Regular Security Updates: Keep Spring Security and dependencies updated
  • Main Security Config: src/main/java/com/proyecto/integrador/configuraciones/MySecurityConfig.java
  • JWT Filter: src/main/java/com/proyecto/integrador/configuraciones/JwtAuthenticationFilter.java
  • Entry Point: src/main/java/com/proyecto/integrador/configuraciones/JwtAuthenticationEntryPoint.java
  • User Details Service: src/main/java/com/proyecto/integrador/servicios/impl/UserDetailsServiceImpl.java

Next Steps

Build docs developers (and LLMs) love