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