Java定时安全流程规范

wen java案例 28

Java定时任务安全流程规范

定时任务基础安全规范

1 任务设计原则

// 安全原则示例
public class SecureScheduledTask {
    // 原则1:幂等性设计
    @Scheduled(cron = "0 0 2 * * ?")
    public void executeIdempotentTask() {
        String taskId = UUID.randomUUID().toString();
        try {
            // 使用分布式锁防止重复执行
            if (distributedLock.tryLock(taskId, 30, TimeUnit.SECONDS)) {
                // 业务逻辑
                processTask();
            }
        } finally {
            distributedLock.unlock(taskId);
        }
    }
    // 原则2:失败重试机制
    @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public void processWithRetry() {
        // 业务处理
    }
}

2 配置管理安全

# application.yml 安全配置
spring:
  task:
    scheduling:
      pool:
        size: 10
      thread-name-prefix: "secure-scheduler-"
  # 加密配置
  encrypt:
    key: ${TASK_ENCRYPT_KEY}
# 定时任务配置
task:
  execution:
    timeout: 300000  # 5分钟超时
    retry:
      max-attempts: 3
      backoff-delay: 1000

身份认证与授权

1 任务执行者认证

@Configuration
@EnableScheduling
public class SecureSchedulingConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("secure-task-");
        // 设置安全上下文传递
        executor.setTaskDecorator(runnable -> {
            // 复制安全上下文
            SecurityContext context = SecurityContextHolder.getContext();
            return () -> {
                try {
                    SecurityContextHolder.setContext(context);
                    runnable.run();
                } finally {
                    SecurityContextHolder.clearContext();
                }
            };
        });
        executor.initialize();
        return executor;
    }
}

2 任务授权校验

@Component
public class SecureTaskExecutor {
    @Autowired
    private TaskAuthorizationService authService;
    public void executeWithAuth(String taskId, Runnable task) {
        // 检查执行权限
        if (!authService.hasPermission(taskId, getCurrentUser())) {
            throw new SecurityException("无权限执行定时任务: " + taskId);
        }
        // 记录执行日志
        TaskExecutionLog log = TaskExecutionLog.builder()
            .taskId(taskId)
            .executor(getCurrentUser())
            .executionTime(LocalDateTime.now())
            .build();
        try {
            task.run();
            log.setStatus(ExecutionStatus.SUCCESS);
        } catch (Exception e) {
            log.setStatus(ExecutionStatus.FAILED);
            log.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            taskExecutionLogRepository.save(log);
        }
    }
    private String getCurrentUser() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return auth != null ? auth.getName() : "SYSTEM";
    }
}

数据安全保护

1 敏感数据加密

@Component
public class TaskDataEncryptor {
    @Value("${task.encryption.key}")
    private String encryptionKey;
    public String encryptTaskData(String data) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(
                encryptionKey.getBytes(StandardCharsets.UTF_8), "AES"
            );
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encrypted = cipher.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new TaskSecurityException("数据加密失败", e);
        }
    }
    public String decryptTaskData(String encryptedData) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(
                encryptionKey.getBytes(StandardCharsets.UTF_8), "AES"
            );
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] decrypted = cipher.doFinal(
                Base64.getDecoder().decode(encryptedData)
            );
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new TaskSecurityException("数据解密失败", e);
        }
    }
}

2 数据库连接安全

# 数据库连接池安全配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/task_db?useSSL=true&requireSSL=true
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      # 连接验证
      connection-test-query: SELECT 1
      validation-timeout: 3000

异常处理与监控

1 全局异常处理

@Component
public class TaskExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(TaskExceptionHandler.class);
    @EventListener
    public void handleTaskExecutionFailure(TaskExecutionFailedEvent event) {
        TaskExecution task = event.getTask();
        Exception exception = event.getException();
        // 记录详细错误信息
        log.error("定时任务执行失败 - 任务ID: {}, 任务名称: {}, 错误类型: {}, 错误信息: {}", 
            task.getId(), 
            task.getName(), 
            exception.getClass().getName(),
            exception.getMessage());
        // 发送告警通知
        sendAlert(task, exception);
        // 更新任务状态
        updateTaskStatus(task, TaskStatus.FAILED);
        // 自动重试逻辑
        if (shouldRetry(task, exception)) {
            retryTask(task);
        }
    }
    private boolean shouldRetry(TaskExecution task, Exception exception) {
        // 仅对可恢复异常进行重试
        return exception instanceof RetryableException 
            && task.getRetryCount() < task.getMaxRetryAttempts();
    }
}

2 监控指标体系

@Component
public class TaskMetricsCollector {
    private final MeterRegistry meterRegistry;
    public TaskMetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    @EventListener
    public void recordTaskMetrics(TaskExecutionEvent event) {
        // 记录执行时间
        Timer.Sample sample = Timer.start(meterRegistry);
        // 记录执行结果
        Counter.builder("task.execution.total")
            .tag("taskName", event.getTaskName())
            .tag("status", event.getStatus().name())
            .register(meterRegistry)
            .increment();
        // 记录执行时长
        Timer.builder("task.execution.duration")
            .tag("taskName", event.getTaskName())
            .register(meterRegistry)
            .record(event.getDuration(), TimeUnit.MILLISECONDS);
        // 记录错误率
        if (event.getStatus() == ExecutionStatus.FAILED) {
            Counter.builder("task.execution.errors")
                .tag("taskName", event.getTaskName())
                .tag("errorType", event.getException().getClass().getName())
                .register(meterRegistry)
                .increment();
        }
    }
}

安全审计与日志

1 操作审计日志

@Component
public class TaskAuditLogger {
    private static final Logger auditLog = LoggerFactory.getLogger("TASK_AUDIT");
    public void logTaskExecution(TaskExecution task) {
        AuditRecord record = AuditRecord.builder()
            .timestamp(LocalDateTime.now())
            .taskId(task.getId())
            .taskName(task.getName())
            .executor(task.getExecutor())
            .executionType(task.getType())
            .status(task.getStatus())
            .duration(task.getDuration())
            .sourceIp(getClientIp())
            .build();
        auditLog.info("定时任务审计日志: {}", toJson(record));
        // 持久化到审计表
        auditRepository.save(record);
    }
    public void logSecurityEvent(SecurityEvent event) {
        auditLog.warn("安全事件告警: {}", toJson(event));
        // 触发安全告警
        alertService.sendSecurityAlert(event);
    }
    private String getClientIp() {
        // 获取当前请求的IP地址
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (attributes instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
            return request.getRemoteAddr();
        }
        return "127.0.0.1";
    }
}

2 日志安全配置

# logback-spring.xml 安全日志配置
logging:
  level:
    TASK_AUDIT: INFO
    com.task.security: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/task-security.log
    max-size: 100MB
    max-history: 30
    total-size-cap: 2GB

分布式环境安全

1 分布式锁实现

@Component
public class DistributedTaskLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String LOCK_PREFIX = "task:lock:";
    private static final long LOCK_TIMEOUT = 30000; // 30秒
    public boolean acquireLock(String taskId, String requestId) {
        String lockKey = LOCK_PREFIX + taskId;
        // 使用SET NX EX实现分布式锁
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, LOCK_TIMEOUT, TimeUnit.MILLISECONDS);
        if (Boolean.TRUE.equals(success)) {
            log.info("获取分布式锁成功 - taskId: {}, requestId: {}", taskId, requestId);
            return true;
        }
        // 检查锁是否过期
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        if (currentValue != null && System.currentTimeMillis() > 
            Long.parseLong(currentValue.split(":")[1])) {
            // 锁已过期,尝试获取
            String oldValue = redisTemplate.opsForValue()
                .getAndSet(lockKey, requestId + ":" + System.currentTimeMillis());
            if (oldValue != null && oldValue.equals(currentValue)) {
                log.info("获取过期锁成功 - taskId: {}", taskId);
                return true;
            }
        }
        return false;
    }
    public void releaseLock(String taskId, String requestId) {
        String lockKey = LOCK_PREFIX + taskId;
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            requestId
        );
    }
}

2 节点间通信安全

@Configuration
public class InterNodeCommunicationConfig {
    @Bean
    public RestTemplate secureRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        // 添加SSL支持
        SSLContext sslContext = createSSLContext();
        CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLContext(sslContext)
            .setSSLHostnameVerifier(new NoopHostnameVerifier())
            .build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
        // 添加请求拦截器添加认证信息
        restTemplate.getInterceptors().add((request, body, execution) -> {
            request.getHeaders().add("Authorization", "Bearer " + getAuthToken());
            request.getHeaders().add("X-Node-ID", getNodeId());
            request.getHeaders().add("X-Request-ID", UUID.randomUUID().toString());
            return execution.execute(request, body);
        });
        return restTemplate;
    }
    private SSLContext createSSLContext() {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(getClass().getResourceAsStream("/keystore.p12"), 
                "password".toCharArray());
            SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, "password".toCharArray())
                .loadTrustMaterial(null, (certificate, authType) -> true)
                .build();
            return sslContext;
        } catch (Exception e) {
            throw new RuntimeException("SSL上下文创建失败", e);
        }
    }
}

任务调度安全策略

1 定时任务时间窗口控制

@Component
public class TimeWindowScheduler {
    private static final Map<String, TimeWindow> TASK_TIME_WINDOWS = new HashMap<>();
    static {
        // 定义任务可执行时间窗口
        TASK_TIME_WINDOWS.put("DATA_SYNC_TASK", 
            new TimeWindow(LocalTime.of(23, 0), LocalTime.of(6, 0)));
        TASK_TIME_WINDOWS.put("REPORT_GENERATION", 
            new TimeWindow(LocalTime.of(8, 0), LocalTime.of(20, 0)));
    }
    @Scheduled(cron = "0 0 2 * * ?")
    public void executeDataSyncTask() {
        if (isWithinTimeWindow("DATA_SYNC_TASK")) {
            // 执行数据同步任务
            dataSyncService.syncData();
        } else {
            log.warn("数据同步任务不在允许的时间窗口内,已阻止执行");
        }
    }
    private boolean isWithinTimeWindow(String taskName) {
        TimeWindow window = TASK_TIME_WINDOWS.get(taskName);
        if (window == null) {
            return true; // 未设置时间窗口则允许执行
        }
        LocalTime now = LocalTime.now();
        if (window.getStart().isBefore(window.getEnd())) {
            return !now.isBefore(window.getStart()) && !now.isAfter(window.getEnd());
        } else {
            // 跨天时间窗口
            return !now.isBefore(window.getStart()) || !now.isAfter(window.getEnd());
        }
    }
    @Data
    @AllArgsConstructor
    private static class TimeWindow {
        private LocalTime start;
        private LocalTime end;
    }
}

2 任务频率控制

@Component
public class TaskRateLimiter {
    private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒10个请求
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    public boolean allowExecution(String taskId) {
        // 本地限流
        if (!rateLimiter.tryAcquire()) {
            log.warn("本地限流触发 - taskId: {}", taskId);
            return false;
        }
        // 分布式限流
        String key = "task:ratelimit:" + taskId;
        Long count = redisTemplate.opsForValue().increment(key);
        if (count == 1) {
            redisTemplate.expire(key, 1, TimeUnit.MINUTES);
        }
        if (count > 100) { // 每分钟最多100次
            log.warn("分布式限流触发 - taskId: {}, count: {}", taskId, count);
            return false;
        }
        return true;
    }
}

应急响应流程

1 异常任务自动处理

@Component
public class TaskEmergencyHandler {
    @EventListener
    public void handleCriticalTaskFailure(CriticalTaskFailureEvent event) {
        // 1. 立即停止相关任务
        stopRelatedTasks(event.getTaskId());
        // 2. 发送紧急告警
        sendEmergencyAlert(event);
        // 3. 自动回滚操作
        if (event.isRollbackable()) {
            rollbackTask(event.getTaskId());
        }
        // 4. 启动备用任务
        startFallbackTask(event.getTaskId());
        // 5. 记录事故报告
        createIncidentReport(event);
    }
    private void stopRelatedTasks(String taskId) {
        List<String> relatedTasks = taskDependencyService.getRelatedTasks(taskId);
        relatedTasks.forEach(tId -> {
            taskScheduler.cancelTask(tId);
            log.info("已停止相关任务: {}", tId);
        });
    }
    private void sendEmergencyAlert(CriticalTaskFailureEvent event) {
        AlertMessage alert = AlertMessage.builder()
            .level(AlertLevel.CRITICAL)
            .title("定时任务严重失败")
            .message(String.format("任务ID: %s, 错误: %s", 
                event.getTaskId(), event.getException().getMessage()))
            .timestamp(LocalDateTime.now())
            .build();
        alertService.sendSMS(getOnCallEngineer(), alert);
        alertService.sendEmail(getEmergencyTeam(), alert);
    }
}

2 应急恢复流程

# 定时任务应急恢复流程
## 1. 故障确认
- [ ] 检查任务执行日志
- [ ] 确认故障范围
- [ ] 评估影响程度
## 2. 立即响应
- [ ] 停止异常任务执行
- [ ] 通知相关团队
- [ ] 开始故障排查
## 3. 数据恢复
- [ ] 检查数据一致性
- [ ] 执行数据修复脚本
- [ ] 验证数据完整性
## 4. 任务恢复
- [ ] 修复代码缺陷
- [ ] 部署修复版本
- [ ] 手动触发任务执行
- [ ] 监控任务执行状态
## 5. 事后分析
- [ ] 编写故障报告
- [ ] 更新应急预案
- [ ] 改进监控告警

合规性检查清单

1 安全检查清单

## 定时任务安全检查清单
### □ 身份认证
- [ ] 任务执行是否需要认证
- [ ] 是否使用服务账号
- [ ] 认证凭证是否安全存储
### □ 授权控制
- [ ] 是否实现RBAC权限模型
- [ ] 敏感操作是否有额外授权
- [ ] 权限变更是否记录审计
### □ 数据安全
- [ ] 敏感数据是否加密
- [ ] 数据库连接是否使用SSL
- [ ] 日志是否脱敏处理
### □ 执行安全
- [ ] 是否实现幂等性
- [ ] 是否有超时控制
- [ ] 是否有限流措施
### □ 监控告警
- [ ] 是否有异常监控
- [ ] 告警是否及时通知
- [ ] 是否有仪表盘展示
### □ 审计日志
- [ ] 是否记录操作日志
- [ ] 日志是否不可篡改
- [ ] 日志保留期限是否合规
### □ 应急响应
- [ ] 是否有应急预案
- [ ] 是否定期演练
- [ ] 回滚机制是否完善

这个Java定时安全流程规范涵盖了身份认证、数据安全、异常处理、监控审计、分布式安全等多个方面,确保定时任务的安全可靠执行,请根据实际业务需求选择适合的组件和配置。

Java定时安全流程规范

抱歉,评论功能暂时关闭!