Security Architecture & Authentication
Overview
The MyTradeX platform implements a comprehensive, multi-layered security architecture designed to protect sensitive trading data, ensure regulatory compliance, and provide robust access control. Security is implemented across all layers of the application, from authentication to data encryption.
Security Philosophy
Core Principles
- Defense in Depth: Multiple security layers and controls
- Zero Trust Architecture: Never trust, always verify
- Principle of Least Privilege: Minimal necessary access rights
- Data Protection: End-to-end encryption for sensitive data
- Audit Trail: Complete security event logging
- Regulatory Compliance: Industry-standard security practices
Security Layers
| Layer | Security Measures |
|---|---|
| Network | Firewall, DDoS protection, VPN |
| Transport | TLS 1.3, certificate pinning |
| Application | Input validation, XSS protection |
| Authentication | JWT tokens, MFA, session management |
| Authorization | Role-based access control (RBAC) |
| Data | Encryption at rest, field-level encryption |
| Infrastructure | Container security, secrets management |
Authentication Architecture
1. JWT-Based Authentication
Token Structure:
interface JWTPayload {
sub: string; // User ID
email: string; // User email
role: string; // User role (TRADER, ADMIN, BROKER)
iat: number; // Issued at
exp: number; // Expires at
jti: string; // JWT ID
permissions: string[]; // Specific permissions
}
JWT Service Implementation:
Location: mytradex-backend/security/src/main/java/com/mytradex/security/JwtService.java
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long jwtExpiration;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("role", user.getRole().name());
claims.put("email", user.getEmail());
claims.put("name", user.getName());
claims.put("permissions", getUserPermissions(user));
return buildToken(claims, user.getId().toString());
}
public boolean isTokenValid(String token) {
try {
Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.warn("Invalid JWT token: {}", e.getMessage());
return false;
}
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public String extractRole(String token) {
return extractClaim(token, claims -> claims.get("role", String.class));
}
}
2. Client-Side Authentication
API Client Configuration:
Location: mytradex-monorepo/packages/api/src/apiClient.ts
export class ApiClient {
private client: AxiosInstance;
constructor(config: ApiConfig) {
this.client = axios.create({
baseURL: config.baseURL,
timeout: config.timeout || 10000,
headers: {
'Content-Type': 'application/json',
...config.headers,
},
});
this.setupInterceptors();
}
private setupInterceptors() {
// Request interceptor for auth tokens
this.client.interceptors.request.use(
(config) => {
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
this.handleUnauthorized();
}
return Promise.reject(error);
}
);
}
private getAuthToken(): string | null {
return localStorage.getItem('authToken');
}
private handleUnauthorized() {
localStorage.removeItem('authToken');
window.location.href = '/login';
}
}
Authentication Hook:
Location: mytradex-monorepo/packages/hooks/src/useAuth.ts
export const useAuth = () => {
const queryClient = useQueryClient();
// Get current user
const {
data: user,
isLoading,
error,
} = useQuery({
queryKey: ['auth', 'user'],
queryFn: async (): Promise<User | null> => {
const token = localStorage.getItem('authToken');
if (!token) return null;
// Validate token and get user info
const response = await apiClient.get<User>('/auth/me');
return response.data;
},
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Login mutation
const loginMutation = useMutation({
mutationFn: async (credentials: LoginCredentials): Promise<User> => {
const response = await apiClient.post<LoginResponse>('/auth/login', credentials);
// Store token
localStorage.setItem('authToken', response.data.token);
return response.data.user;
},
onSuccess: (user) => {
queryClient.setQueryData(['auth', 'user'], user);
socketClient.connect();
},
});
// Logout mutation
const logoutMutation = useMutation({
mutationFn: async (): Promise<void> => {
localStorage.removeItem('authToken');
socketClient.disconnect();
},
onSuccess: () => {
queryClient.setQueryData(['auth', 'user'], null);
queryClient.clear();
},
});
return {
user: user || null,
isAuthenticated: !!user,
isLoading,
login: loginMutation.mutate,
logout: logoutMutation.mutate,
isLoggingIn: loginMutation.isPending,
loginError: loginMutation.error,
};
};
3. Spring Security Configuration
Security Configuration:
Location: mytradex-backend/security/src/main/java/com/mytradex/security/SecurityConfig.java
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/public/**").permitAll()
.requestMatchers(HttpMethod.GET, "/market-data/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.headers(headers -> headers
.frameOptions().deny()
.contentTypeOptions(Customizer.withDefaults())
.httpStrictTransportSecurity(hstsConfig -> hstsConfig
.maxAgeInSeconds(31536000)
.includeSubdomains(true)
)
);
return http.build();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. JWT Authentication Filter
Filter Implementation:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
userEmail = jwtService.extractUsername(jwt);
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
Authorization Architecture
1. Role-Based Access Control (RBAC)
User Roles Hierarchy:
public enum UserRole {
TRADER("trader", "Basic trading permissions"),
ADMIN("admin", "User management and reporting"),
BROKER("broker", "Full administrative privileges");
private final String roleName;
private final String description;
UserRole(String roleName, String description) {
this.roleName = roleName;
this.description = description;
}
public List<Permission> getPermissions() {
return switch (this) {
case TRADER -> Arrays.asList(
Permission.READ_MARKET_DATA,
Permission.CREATE_ORDERS,
Permission.READ_OWN_ORDERS,
Permission.READ_OWN_POSITIONS
);
case ADMIN -> Arrays.asList(
Permission.READ_MARKET_DATA,
Permission.CREATE_ORDERS,
Permission.READ_OWN_ORDERS,
Permission.READ_OWN_POSITIONS,
Permission.MANAGE_USERS,
Permission.READ_AUDIT_LOGS,
Permission.GENERATE_REPORTS
);
case BROKER -> Arrays.asList(
Permission.values() // All permissions
);
};
}
}
Permission Enum:
public enum Permission {
READ_MARKET_DATA("read:market_data"),
CREATE_ORDERS("create:orders"),
READ_OWN_ORDERS("read:orders:own"),
READ_ALL_ORDERS("read:orders:all"),
UPDATE_ORDERS("update:orders"),
DELETE_ORDERS("delete:orders"),
READ_OWN_POSITIONS("read:positions:own"),
READ_ALL_POSITIONS("read:positions:all"),
MANAGE_USERS("manage:users"),
READ_AUDIT_LOGS("read:audit_logs"),
GENERATE_REPORTS("generate:reports"),
SYSTEM_ADMIN("system:admin");
private final String permission;
Permission(String permission) {
this.permission = permission;
}
public String getPermission() {
return permission;
}
}
2. Method-Level Security
Service Layer Security:
@Service
@Transactional
public class OrderService {
@PreAuthorize("hasAuthority('CREATE_ORDERS')")
public Order createOrder(CreateOrderRequest request) {
// Order creation logic
}
@PreAuthorize("hasAuthority('READ_ORDERS_ALL') or " +
"(hasAuthority('READ_ORDERS_OWN') and #userId == authentication.principal.id)")
public List<Order> getUserOrders(UUID userId) {
// Order retrieval logic
}
@PreAuthorize("hasAuthority('UPDATE_ORDERS') or " +
"(hasAuthority('UPDATE_ORDERS_OWN') and @orderService.isOwner(#orderId, authentication.principal.id))")
public Order updateOrder(UUID orderId, UpdateOrderRequest request) {
// Order update logic
}
@PreAuthorize("hasAuthority('DELETE_ORDERS') or " +
"(hasAuthority('DELETE_ORDERS_OWN') and @orderService.isOwner(#orderId, authentication.principal.id))")
public void cancelOrder(UUID orderId) {
// Order cancellation logic
}
}
Admin Service Security:
@Service
@PreAuthorize("hasAuthority('MANAGE_USERS')")
public class UserManagementService {
@PreAuthorize("hasAuthority('READ_AUDIT_LOGS')")
public List<AuditLog> getAuditLogs(AuditLogFilter filter) {
// Audit log retrieval
}
@PreAuthorize("hasAuthority('GENERATE_REPORTS')")
public Report generateTradingReport(ReportRequest request) {
// Report generation
}
}
3. Frontend Route Protection
Route Guards:
Location: src/app/(auth)/layout.tsx
'use client';
import { useAuth } from '@/hooks/useAuth';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export function AuthLayout({ children }: { children: React.ReactNode }) {
const { user, isAuthenticated, isLoading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.push('/login');
}
}, [isAuthenticated, isLoading, router]);
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return null;
}
return <>{children}</>;
}
Role-Based Component Access:
interface ProtectedRouteProps {
children: React.ReactNode;
requiredRole?: 'TRADER' | 'ADMIN' | 'BROKER';
requiredPermission?: string;
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
requiredRole,
requiredPermission
}) => {
const { user } = useAuth();
const hasPermission = () => {
if (requiredPermission && user?.permissions) {
return user.permissions.includes(requiredPermission);
}
if (requiredRole && user?.role) {
const roleHierarchy = {
'TRADER': 0,
'ADMIN': 1,
'BROKER': 2
};
return roleHierarchy[user.role] >= roleHierarchy[requiredRole];
}
return true;
};
if (!hasPermission()) {
return <AccessDenied />;
}
return <>{children}</>;
};
// Usage in components
<ProtectedRoute requiredRole="ADMIN">
<AdminDashboard />
</ProtectedRoute>
<ProtectedRoute requiredPermission="CREATE_ORDERS">
<OrderForm />
</ProtectedRoute>
Data Security
1. Encryption at Rest
Database Encryption:
-- Transparent Data Encryption (TDE)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Encrypt sensitive columns
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
ssn_encrypted VARCHAR(255), -- Encrypted Social Security Number
api_key_encrypted VARCHAR(255) -- Encrypted API key
);
-- Function to encrypt sensitive data
CREATE OR REPLACE FUNCTION encrypt_sensitive_data(data TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN encode(encrypt(data::bytea, 'encryption_key', 'aes'), 'base64');
END;
$$ LANGUAGE plpgsql;
-- Function to decrypt sensitive data
CREATE OR REPLACE FUNCTION decrypt_sensitive_data(encrypted_data TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN convert_from(decrypt(decode(encrypted_data, 'base64'), 'encryption_key', 'aes'), 'UTF8');
END;
$$ LANGUAGE plpgsql;
Application-Level Encryption:
@Service
public class EncryptionService {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final String KEY_ALGORITHM = "AES";
private static final int KEY_LENGTH = 256;
private final SecretKey secretKey;
public EncryptionService(@Value("${encryption.key}") String key) {
this.secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), KEY_ALGORITHM);
}
public String encrypt(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedData = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
byte[] iv = cipher.getIV();
// Combine IV and encrypted data
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String encryptedText) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedText);
byte[] iv = new byte[12]; // GCM IV length
byte[] encryptedData = new byte[combined.length - iv.length];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, encryptedData, 0, encryptedData.length);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec);
byte[] decryptedData = cipher.doFinal(encryptedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
}
2. Transport Layer Security
TLS Configuration:
# Application SSL/TLS Configuration
server:
ssl:
enabled: true
key-store: classpath:keystore/mytradex.jks
key-store-password: ${SSL_KEYSTORE_PASSWORD}
key-store-type: JKS
key-alias: mytradex
protocol: TLSv1.3
ciphers: |
TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256,
TLS_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
enabled-protocols: TLSv1.3
# WebSocket SSL Configuration
spring:
websocket:
allowed-origins: https://mytradex.id
ssl:
enabled: true
key-store: classpath:keystore/mytradex.jks
key-store-password: ${SSL_KEYSTORE_PASSWORD}
Client-Side Certificate Pinning:
// Next.js API configuration with certificate pinning
const apiClient = axios.create({
baseURL: 'https://api.mytradex.id',
httpsAgent: new https.Agent({
ca: [fs.readFileSync('./certs/mytradex-root-ca.pem')],
checkServerIdentity: (host, cert) => {
const allowedHosts = ['api.mytradex.id'];
const allowedCerts = ['sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='];
if (!allowedHosts.includes(host)) {
throw new Error(`Unauthorized host: ${host}`);
}
const certFingerprint = `sha256/${cert.fingerprint256}`;
if (!allowedCerts.includes(certFingerprint)) {
throw new Error(`Unauthorized certificate: ${certFingerprint}`);
}
}
})
});
3. Input Validation & Sanitization
Backend Validation:
@Entity
public class Order {
@NotBlank(message = "Symbol is required")
@Pattern(regexp = "^[A-Z]{1,10}$", message = "Invalid symbol format")
private String symbol;
@NotNull(message = "Quantity is required")
@DecimalMin(value = "0.00000001", message = "Quantity must be positive")
@DecimalMax(value = "1000000", message = "Quantity exceeds maximum limit")
private BigDecimal quantity;
@DecimalMin(value = "0.00000001", message = "Price must be positive")
private BigDecimal price;
// Custom validation
@AssertTrue(message = "Price is required for limit orders")
public boolean isPriceValidForOrderType() {
if (orderType == OrderType.LIMIT && price == null) {
return false;
}
return true;
}
}
Frontend Validation:
// Order form validation
const orderSchema = z.object({
symbol: z.string()
.min(1, "Symbol is required")
.max(10, "Symbol too long")
.regex(/^[A-Z]+$/, "Invalid symbol format"),
quantity: z.number()
.positive("Quantity must be positive")
.max(1000000, "Quantity exceeds limit"),
price: z.number().positive("Price must be positive").optional(),
orderType: z.enum(['MARKET', 'LIMIT', 'STOP', 'STOP_LIMIT']),
side: z.enum(['BUY', 'SELL'])
}).refine((data) => {
if (data.orderType === 'LIMIT' && !data.price) {
return false;
}
return true;
}, {
message: "Price is required for limit orders",
path: ['price']
});
function OrderForm() {
const form = useForm<OrderFormData>({
resolver: zodResolver(orderSchema),
defaultValues: {
symbol: '',
quantity: 0,
price: undefined,
orderType: 'LIMIT',
side: 'BUY'
}
});
// Form submission with validation
const onSubmit = async (data: OrderFormData) => {
try {
await orderMutation.mutateAsync(data);
} catch (error) {
console.error('Order submission failed:', error);
}
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<input
{...form.register('symbol')}
placeholder="Symbol"
className="form-input"
/>
{form.formState.errors.symbol && (
<span className="error">{form.formState.errors.symbol.message}</span>
)}
<input
type="number"
step="0.00000001"
{...form.register('quantity', { valueAsNumber: true })}
placeholder="Quantity"
className="form-input"
/>
{form.watch('orderType') === 'LIMIT' && (
<input
type="number"
step="0.01"
{...form.register('price', { valueAsNumber: true })}
placeholder="Price"
className="form-input"
/>
)}
<button type="submit">Place Order</button>
</form>
);
}
API Security
1. Rate Limiting
Spring Boot Rate Limiting:
@Component
public class RateLimitingInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, String> redisTemplate;
private final RateLimiter rateLimiter = RateLimiter.create(100); // 100 requests per second
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String clientId = getClientId(request);
if (!rateLimiter.tryAcquire()) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Rate limit exceeded");
return false;
}
// Redis-based rate limiting for API endpoints
String key = "rate_limit:" + clientId + ":" + request.getRequestURI();
String current = redisTemplate.opsForValue().get(key);
if (current != null && Integer.parseInt(current) >= MAX_REQUESTS_PER_MINUTE) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
return false;
}
redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, Duration.ofMinutes(1));
return true;
}
}
Frontend Rate Limiting:
class APICallThrottler {
private requestQueue: Array<() => Promise<any>> = [];
private isProcessing = false;
private readonly maxRequestsPerSecond = 10;
private requestCount = 0;
private lastResetTime = Date.now();
async throttle<T>(requestFn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue() {
if (this.isProcessing || this.requestQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.requestQueue.length > 0) {
// Reset counter every second
if (Date.now() - this.lastResetTime >= 1000) {
this.requestCount = 0;
this.lastResetTime = Date.now();
}
if (this.requestCount >= this.maxRequestsPerSecond) {
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
const request = this.requestQueue.shift()!;
await request();
this.requestCount++;
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 100));
}
this.isProcessing = false;
}
}
2. CORS Configuration
Backend CORS Configuration:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://mytradex.id", "https://admin.mytradex.id")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("Authorization", "Content-Type", "X-Requested-With")
.exposedHeaders("Authorization", "X-Total-Count", "X-Rate-Limit-Remaining")
.allowCredentials(true)
.maxAge(3600);
}
}
Frontend CORS Handling:
// Next.js CORS proxy configuration
const corsHeaders = {
'Access-Control-Allow-Origin': process.env.NEXT_PUBLIC_APP_URL,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true'
};
export async function handleCorsRequest(req: Request) {
if (req.method === 'OPTIONS') {
return new Response(null, { status: 200, headers: corsHeaders });
}
}
3. Content Security Policy (CSP)
CSP Headers Configuration:
@Configuration
public class SecurityHeadersConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://cdn.mytradex.id; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self' wss://api.mytradex.id https://api.mytradex.id; " +
"frame-ancestors 'none'; " +
"base-uri 'self';")
)
.referrerPolicy(referrerPolicy -> referrerPolicy
.policy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
)
.frameOptions(frameOptions -> frameOptions
.deny()
)
.httpStrictTransportSecurity(hstsConfig -> hstsConfig
.maxAgeInSeconds(31536000)
.includeSubdomains(true)
.preload(true)
)
);
return http.build();
}
}
Security Monitoring & Compliance
1. Security Event Logging
Audit Log Service:
@Service
public class AuditLogService {
private final AuditLogRepository auditLogRepository;
@Async
public void logSecurityEvent(String userId, String action, String resource,
Map<String, Object> details, HttpServletRequest request) {
AuditLog auditLog = AuditLog.builder()
.userId(userId != null ? UUID.fromString(userId) : null)
.action(action)
.resourceType(resource)
.ipAddress(getClientIpAddress(request))
.userAgent(request.getHeader("User-Agent"))
.details(details)
.timestamp(LocalDateTime.now())
.build();
auditLogRepository.save(auditLog);
}
public void logAuthenticationEvent(String userId, String action, String status,
String failureReason, HttpServletRequest request) {
logSecurityEvent(userId, "AUTHENTICATION_" + action, "AUTH",
Map.of("status", status, "failureReason", failureReason), request);
}
public void logAuthorizationEvent(String userId, String resource, String action,
String decision, HttpServletRequest request) {
logSecurityEvent(userId, "AUTHORIZATION", resource,
Map.of("action", action, "decision", decision), request);
}
}
Frontend Security Monitoring:
// Security event tracking
class SecurityMonitor {
static trackLoginAttempt(email: string, success: boolean, method: string = 'password') {
this.sendEvent('LOGIN_ATTEMPT', {
email: this.hashEmail(email),
success,
method,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
ipAddress: this.getClientIP()
});
}
static trackPermissionDenied(resource: string, action: string) {
this.sendEvent('PERMISSION_DENIED', {
resource,
action,
timestamp: new Date().toISOString(),
userId: this.getCurrentUserId()
});
}
static trackSuspiciousActivity(activity: string, details: any) {
this.sendEvent('SUSPICIOUS_ACTIVITY', {
activity,
details,
timestamp: new Date().toISOString(),
userId: this.getCurrentUserId(),
sessionId: this.getSessionId()
});
}
private static async sendEvent(eventType: string, data: any) {
try {
await fetch('/api/security/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`
},
body: JSON.stringify({ eventType, data })
});
} catch (error) {
console.error('Failed to send security event:', error);
}
}
}
2. Compliance & Regulatory
Data Retention Policy:
@Service
public class DataRetentionService {
@Scheduled(cron = "0 0 2 * * ?") // Daily at 2 AM
@Transactional
public void enforceRetentionPolicies() {
// Delete old audit logs (7 years)
LocalDateTime cutoffDate = LocalDateTime.now().minusYears(7);
auditLogRepository.deleteByTimestampBefore(cutoffDate);
// Archive old market data (1 year)
LocalDateTime archiveDate = LocalDateTime.now().minusYears(1);
marketDataService.archiveOldData(archiveDate);
// Clean up expired sessions
sessionService.deleteExpiredSessions();
}
}
Regulatory Compliance Features:
@Entity
public class ComplianceReport {
@Id
private UUID id;
private String reportType; // SUSPICIOUS_ACTIVITY, TRADE_BLOTTER, etc.
private LocalDateTime reportDate;
private String generatedBy;
private String filePath;
private String status; // GENERATED, REVIEWED, SUBMITTED
private Map<String, Object> metadata;
// Compliance-specific methods
public void markAsSubmitted(String regulator) {
this.status = "SUBMITTED";
this.metadata.put("submittedTo", regulator);
this.metadata.put("submittedAt", LocalDateTime.now());
}
}
Next: Architecture Diagrams
Continue to Architecture Diagrams for visual representations of the system architecture and data flows.