Skip to content

Latest commit

ย 

History

History
596 lines (476 loc) ยท 17.8 KB

api-security.md

File metadata and controls

596 lines (476 loc) ยท 17.8 KB

API ๋ณด์•ˆ ์‹œ์Šคํ…œ ์„ค๊ณ„

1. API ์ธ์ฆ๊ณผ ์ธ๊ฐ€ ์‹œ์Šคํ…œ

@Configuration
public class ApiSecurityConfig {

    // 1. API ํ‚ค ๊ด€๋ฆฌ
    @Service
    public class ApiKeyManager {
        private final ApiKeyRepository apiKeyRepository;
        private final RedisTemplate<String, ApiKeyDetails> redisTemplate;

        public ApiKey generateApiKey(String clientId, ApiKeySpec spec) {
            String apiKey = generateSecureRandomKey();
            String hashedKey = hashApiKey(apiKey);

            // API ํ‚ค ์ €์žฅ
            ApiKey key = ApiKey.builder()
                .clientId(clientId)
                .hashedKey(hashedKey)
                .permissions(spec.getPermissions())
                .rateLimit(spec.getRateLimit())
                .expiryDate(spec.getExpiryDate())
                .build();

            apiKeyRepository.save(key);
            cacheApiKey(apiKey, key);

            return key;
        }

        private void cacheApiKey(String apiKey, ApiKey details) {
            String cacheKey = "api_key:" + hashApiKey(apiKey);
            redisTemplate.opsForValue().set(
                cacheKey,
                details,
                Duration.ofHours(24)
            );
        }

        public ApiKeyDetails validateApiKey(String apiKey) {
            String hashedKey = hashApiKey(apiKey);
            ApiKeyDetails details = getFromCache(hashedKey);

            if (details == null) {
                details = apiKeyRepository.findByHashedKey(hashedKey)
                    .orElseThrow(() -> new InvalidApiKeyException(
                        "Invalid API key"));
                cacheApiKey(apiKey, details);
            }

            if (isApiKeyExpired(details)) {
                throw new ApiKeyExpiredException("API key expired");
            }

            return details;
        }
    }

    // 2. ์š”์ฒญ ์ธ์ฆ ํ•„ํ„ฐ
    @Component
    public class ApiAuthenticationFilter extends OncePerRequestFilter {
        
        private final ApiKeyManager apiKeyManager;
        private final RateLimiter rateLimiter;

        @Override
        protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws ServletException, IOException {

            try {
                String apiKey = extractApiKey(request);
                ApiKeyDetails keyDetails = apiKeyManager.validateApiKey(apiKey);

                // ๋น„์œจ ์ œํ•œ ํ™•์ธ
                if (!rateLimiter.tryAcquire(keyDetails.getClientId())) {
                    throw new RateLimitExceededException(
                        "Rate limit exceeded");
                }

                // ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ ์„ค์ •
                SecurityContextHolder.getContext()
                    .setAuthentication(createAuthentication(keyDetails));

                // ์š”์ฒญ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ธฐ๋ก
                auditRequest(request, keyDetails);

            } catch (ApiSecurityException e) {
                handleSecurityException(response, e);
                return;
            }

            chain.doFilter(request, response);
        }

        private String extractApiKey(HttpServletRequest request) {
            String apiKey = request.getHeader("X-API-Key");
            if (apiKey == null) {
                apiKey = request.getParameter("api_key");
            }
            if (apiKey == null) {
                throw new MissingApiKeyException("API key is required");
            }
            return apiKey;
        }
    }
}

2. ์š”์ฒญ ๊ฒ€์ฆ ๋ฐ ๋ณด์•ˆ ์ฒ˜๋ฆฌ

@Service
public class ApiSecurityService {

    // 1. ์š”์ฒญ ๊ฒ€์ฆ
    @Component
    public class RequestValidator {
        private final SignatureVerifier signatureVerifier;
        private final RequestSanitizer requestSanitizer;

        public void validateRequest(HttpServletRequest request) {
            // 1. ์š”์ฒญ ์„œ๋ช… ๊ฒ€์ฆ
            verifyRequestSignature(request);

            // 2. ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋ฐ ์‚ด๊ท 
            sanitizeRequest(request);

            // 3. ์š”์ฒญ ํƒ€์ž„์Šคํƒฌํ”„ ๊ฒ€์ฆ
            validateTimestamp(request);
        }

        private void verifyRequestSignature(HttpServletRequest request) {
            String signature = request.getHeader("X-Signature");
            String timestamp = request.getHeader("X-Timestamp");
            String payload = extractRequestPayload(request);

            if (!signatureVerifier.verify(payload, signature, timestamp)) {
                throw new InvalidSignatureException(
                    "Invalid request signature");
            }
        }

        private void validateTimestamp(HttpServletRequest request) {
            String timestamp = request.getHeader("X-Timestamp");
            long requestTime = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis();

            if (Math.abs(currentTime - requestTime) > 
                TimeUnit.MINUTES.toMillis(5)) {
                throw new RequestTimestampException(
                    "Request timestamp too old");
            }
        }
    }

    // 2. ์ž…๋ ฅ ๊ฒ€์ฆ ๋ฐ ์‚ด๊ท 
    @Component
    public class RequestSanitizer {
        
        public String sanitizeInput(String input) {
            // XSS ๋ฐฉ์ง€
            input = stripXSS(input);
            
            // SQL Injection ๋ฐฉ์ง€
            input = escapeSqlCharacters(input);
            
            // ๋ฌธ์ž ์ธ์ฝ”๋”ฉ ์ •๊ทœํ™”
            input = normalizeString(input);
            
            return input;
        }

        private String stripXSS(String input) {
            // HTML ํƒœ๊ทธ ์ œ๊ฑฐ
            input = input.replaceAll("<[^>]*>", "");
            
            // ์Šคํฌ๋ฆฝํŠธ ์ด๋ฒคํŠธ ์ œ๊ฑฐ
            input = input.replaceAll("javascript:", "");
            input = input.replaceAll("on\\w+\\s*=", "");
            
            return input;
        }

        private String escapeSqlCharacters(String input) {
            return input.replace("'", "''")
                       .replace("\\", "\\\\")
                       .replace("%", "\\%")
                       .replace("_", "\\_");
        }
    }
}

## 3. API ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋ณด์•ˆ ๊ฐ์‚ฌ

```java
@Service
public class ApiMonitoringService {

    // 1. ์š”์ฒญ ๋ชจ๋‹ˆํ„ฐ๋ง
    @Component
    public class RequestMonitor {
        private final MeterRegistry meterRegistry;
        private final AnomalyDetector anomalyDetector;

        @Async
        public void monitorRequest(
            ApiRequest request, 
            ApiResponse response) {
            
            // ์‘๋‹ต ์‹œ๊ฐ„ ๊ธฐ๋ก
            recordResponseTime(request, response);
            
            // ์˜ค๋ฅ˜์œจ ๋ชจ๋‹ˆํ„ฐ๋ง
            if (response.isError()) {
                recordError(request, response);
            }
            
            // ๋น„์ •์ƒ ํŒจํ„ด ๊ฐ์ง€
            if (anomalyDetector.detectAnomaly(request)) {
                handleAnomaly(request);
            }
        }

        private void recordResponseTime(
            ApiRequest request, 
            ApiResponse response) {
            
            meterRegistry.timer("api.response.time",
                "endpoint", request.getEndpoint(),
                "client", request.getClientId())
                .record(response.getResponseTime());
        }
    }

    // 2. ๋ณด์•ˆ ๊ฐ์‚ฌ
    @Service
    public class SecurityAuditService {
        private final AuditEventRepository auditRepository;
        private final AlertService alertService;

        public void auditSecurityEvent(SecurityEvent event) {
            // ๊ฐ์‚ฌ ๋กœ๊ทธ ์ €์žฅ
            AuditLog log = AuditLog.builder()
                .eventType(event.getType())
                .clientId(event.getClientId())
                .endpoint(event.getEndpoint())
                .ipAddress(event.getIpAddress())
                .timestamp(Instant.now())
                .details(event.getDetails())
                .build();

            auditRepository.save(log);

            // ์‹ฌ๊ฐ๋„์— ๋”ฐ๋ฅธ ์•Œ๋ฆผ
            if (event.getSeverity().isHigh()) {
                alertService.sendSecurityAlert(event);
            }
        }

        @Scheduled(fixedRate = 300000) // 5๋ถ„๋งˆ๋‹ค
        public void analyzeSecurityPatterns() {
            List<AuditLog> recentLogs = 
                auditRepository.findRecentLogs(Duration.ofMinutes(5));
                
            SecurityAnalysis analysis = 
                analyzeSecurityPatterns(recentLogs);
                
            if (analysis.hasThreats()) {
                handleSecurityThreats(analysis.getThreats());
            }
        }
    }
}

์ด๋Ÿฌํ•œ API ๋ณด์•ˆ ์‹œ์Šคํ…œ์„ ํ†ตํ•ด:

  1. ์ธ์ฆ/์ธ๊ฐ€

    • API ํ‚ค ๊ด€๋ฆฌ
    • ์š”์ฒญ ๊ฒ€์ฆ
    • ์ ‘๊ทผ ์ œ์–ด
  2. ์š”์ฒญ ๋ณด์•ˆ

    • ์„œ๋ช… ๊ฒ€์ฆ
    • ์ž…๋ ฅ ๊ฒ€์ฆ
    • XSS/SQL Injection ๋ฐฉ์ง€
  3. ๋ชจ๋‹ˆํ„ฐ๋ง/๊ฐ์‚ฌ

    • ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง
    • ๋ณด์•ˆ ๊ฐ์‚ฌ
    • ์œ„ํ˜‘ ๊ฐ์ง€

ํŠนํžˆ ์ค‘์š”ํ•œ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ:

  • API ํ‚ค ์•ˆ์ „์„ฑ
  • ์š”์ฒญ ๋ฌด๊ฒฐ์„ฑ
  • ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ
  • ์ด์ƒ ํ–‰์œ„ ๊ฐ์ง€

๋ฉด์ ‘๊ด€: ์‹ค์ œ ์„œ๋น„์Šค์—์„œ API ๋ณด์•ˆ์„ ๊ตฌํ˜„ํ•  ๋•Œ์˜ ์„ฑ๋Šฅ๊ณผ ๋ณด์•ˆ ์‚ฌ์ด์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

API ๋ณด์•ˆ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต

1. ์บ์‹ฑ ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”

@Service
public class ApiSecurityOptimizer {

    // 1. ๋‹ค์ธต ์บ์‹ฑ ์ „๋žต
    @Service
    public class SecurityCacheManager {
        private final LoadingCache<String, ApiKeyDetails> localCache;
        private final RedisTemplate<String, ApiKeyDetails> redisCache;

        public SecurityCacheManager() {
            this.localCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(Duration.ofMinutes(5))
                .recordStats() // ์บ์‹œ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
                .build(key -> loadFromRedis(key));
        }

        public ApiKeyDetails getApiKeyDetails(String apiKey) {
            String cacheKey = "api_key:" + hashApiKey(apiKey);
            
            try {
                // L1 ์บ์‹œ (๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ)
                return localCache.get(cacheKey);
            } catch (Exception e) {
                // L2 ์บ์‹œ (Redis)
                ApiKeyDetails details = redisCache.opsForValue().get(cacheKey);
                if (details != null) {
                    localCache.put(cacheKey, details);
                    return details;
                }
                // DB ์กฐํšŒ
                return loadFromDatabase(apiKey);
            }
        }

        // ์บ์‹œ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
        @Scheduled(fixedRate = 60000)
        public void monitorCachePerformance() {
            CacheStats stats = localCache.stats();
            
            // ํžˆํŠธ์œจ์ด ๋‚ฎ์€ ๊ฒฝ์šฐ ์บ์‹œ ํฌ๊ธฐ ์กฐ์ •
            if (stats.hitRate() < 0.7) {
                adjustCacheSize(stats);
            }
        }
    }

    // 2. ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์ตœ์ ํ™”
    @Service
    public class ParallelSecurityProcessor {
        private final ExecutorService executorService;

        public CompletableFuture<SecurityValidationResult> validateRequestAsync(
            HttpServletRequest request) {
            
            return CompletableFuture.supplyAsync(() -> {
                List<CompletableFuture<ValidationResult>> futures = Arrays.asList(
                    validateSignature(request),
                    validateApiKey(request),
                    validateRateLimit(request)
                );

                return futures.stream()
                    .map(CompletableFuture::join)
                    .reduce(ValidationResult::combine)
                    .orElseThrow();
            }, executorService);
        }
    }
}

2. ์š”์ฒญ ์ฒ˜๋ฆฌ ์ตœ์ ํ™”

@Service
public class OptimizedRequestProcessor {

    // 1. ๊ฒฝ๋Ÿ‰ํ™”๋œ ๋ณด์•ˆ ์ฒดํฌ
    @Component
    public class LightweightSecurityChecker {
        private final BloomFilter<String> blacklistedTokens;
        private final RateLimiter rateLimiter;

        public ValidationResult quickSecurityCheck(HttpServletRequest request) {
            // ๋ธ”๋ฃธ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ ๋น ๋ฅธ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ฒดํฌ
            String token = request.getHeader("Authorization");
            if (blacklistedTokens.mightContain(token)) {
                return ValidationResult.failed("Potentially blacklisted token");
            }

            // ํ† ํฐ ๊ตฌ์กฐ ๋น ๋ฅธ ๊ฒ€์ฆ
            if (!isValidTokenFormat(token)) {
                return ValidationResult.failed("Invalid token format");
            }

            return ValidationResult.success();
        }
    }

    // 2. ์šฐ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ
    @Component
    public class PriorityRequestHandler {
        
        public void processRequest(HttpServletRequest request) {
            SecurityLevel securityLevel = determineSecurityLevel(request);
            
            switch (securityLevel) {
                case HIGH:
                    applyFullSecurityChecks(request);
                    break;
                case MEDIUM:
                    applyStandardSecurityChecks(request);
                    break;
                case LOW:
                    applyBasicSecurityChecks(request);
                    break;
            }
        }

        private SecurityLevel determineSecurityLevel(
            HttpServletRequest request) {
            
            return SecurityLevel.valueOf(
                Optional.ofNullable(request.getHeader("X-Security-Level"))
                    .orElse(DEFAULT_SECURITY_LEVEL)
            );
        }
    }
}

3. ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ ์ตœ์ ํ™”

@Service
public class ResourceOptimizer {

    // 1. ๋™์  ์Šค๋ ˆ๋“œ ํ’€ ๊ด€๋ฆฌ
    @Component
    public class DynamicThreadPoolManager {
        private final ThreadPoolExecutor executor;
        private final LoadMonitor loadMonitor;

        @Scheduled(fixedRate = 1000)
        public void adjustThreadPool() {
            SystemLoad currentLoad = loadMonitor.getCurrentLoad();
            
            int optimalThreads = calculateOptimalThreads(currentLoad);
            
            executor.setCorePoolSize(optimalThreads);
            executor.setMaximumPoolSize(optimalThreads * 2);
        }

        private int calculateOptimalThreads(SystemLoad load) {
            int processors = Runtime.getRuntime().availableProcessors();
            double loadFactor = Math.min(load.getCpuUsage(), 0.8);
            
            return (int) (processors * loadFactor);
        }
    }

    // 2. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ตœ์ ํ™”
    @Component
    public class MemoryOptimizer {
        
        public void optimizeRequestProcessing(HttpServletRequest request) {
            // ์š”์ฒญ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ์ฒ˜๋ฆฌ
            if (isLargePayload(request)) {
                processLargePayloadInChunks(request);
            } else {
                processNormalPayload(request);
            }
        }

        private void processLargePayloadInChunks(HttpServletRequest request) {
            try (BufferedReader reader = request.getReader()) {
                StringBuilder chunk = new StringBuilder();
                char[] buffer = new char[8192];
                int bytesRead;
                
                while ((bytesRead = reader.read(buffer)) != -1) {
                    chunk.append(buffer, 0, bytesRead);
                    
                    if (chunk.length() >= CHUNK_SIZE) {
                        processChunk(chunk.toString());
                        chunk.setLength(0);
                    }
                }
                
                if (chunk.length() > 0) {
                    processChunk(chunk.toString());
                }
            }
        }
    }
}

4. ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์ตœ์ ํ™” ํ”ผ๋“œ๋ฐฑ

@Service
public class PerformanceMonitor {

    // 1. ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘
    @Component
    public class SecurityMetricsCollector {
        private final MeterRegistry registry;

        public void recordSecurityCheck(String checkType, long duration) {
            registry.timer("security.check.duration",
                "type", checkType)
                .record(duration, TimeUnit.MILLISECONDS);
        }

        @Scheduled(fixedRate = 60000)
        public void analyzePerformance() {
            Map<String, Double> averageDurations = 
                calculateAverageDurations();
                
            // ์„ฑ๋Šฅ ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ๊ฒ€์‚ฌ
            averageDurations.forEach((checkType, avgDuration) -> {
                if (avgDuration > getThreshold(checkType)) {
                    optimizeSecurityCheck(checkType);
                }
            });
        }
    }

    // 2. ์ž๋™ ์ตœ์ ํ™”
    @Service
    public class AutoOptimizer {
        
        public void optimizeBasedOnMetrics() {
            SecurityMetrics metrics = collectCurrentMetrics();
            
            // ์บ์‹œ ์„ค์ • ์ตœ์ ํ™”
            if (metrics.getCacheHitRate() < 0.8) {
                adjustCacheSettings(metrics);
            }

            // ์Šค๋ ˆ๋“œ ํ’€ ์ตœ์ ํ™”
            if (metrics.getThreadPoolUtilization() > 0.9) {
                adjustThreadPool(metrics);
            }

            // ํƒ€์ž„์•„์›ƒ ์„ค์ • ์ตœ์ ํ™”
            if (metrics.getTimeoutRate() > 0.01) {
                adjustTimeouts(metrics);
            }
        }

        private void adjustCacheSettings(SecurityMetrics metrics) {
            int optimalSize = calculateOptimalCacheSize(
                metrics.getCacheStats());
                
            cacheManager.resize(optimalSize);
        }
    }
}

์ด๋Ÿฌํ•œ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต์„ ํ†ตํ•ด:

  1. ์บ์‹ฑ ์ตœ์ ํ™”

    • ๋‹ค์ธต ์บ์‹ฑ
    • ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ
    • ์บ์‹œ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
  2. ์ฒ˜๋ฆฌ ์ตœ์ ํ™”

    • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
    • ์šฐ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ
    • ๊ฒฝ๋Ÿ‰ํ™”๋œ ๊ฒ€์ฆ
  3. ๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ

    • ๋™์  ์Šค๋ ˆ๋“œํ’€
    • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ตœ์ ํ™”
    • ์ฒญํฌ ์ฒ˜๋ฆฌ
  4. ๋ชจ๋‹ˆํ„ฐ๋ง

    • ์‹ค์‹œ๊ฐ„ ์„ฑ๋Šฅ ์ถ”์ 
    • ์ž๋™ ์ตœ์ ํ™”
    • ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„

์ด๋ฅผ ํ†ตํ•ด ๋ณด์•ˆ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ๋†’์€ ์„ฑ๋Šฅ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.