You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
65 lines
2.8 KiB
65 lines
2.8 KiB
package com.mydreamjob.audit;
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Propagation;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
/**
|
|
* 安全审计服务:以独立事务({@code REQUIRES_NEW})写入拒绝型安全事件,
|
|
* 即使外层业务事务因 403/429 等异常回滚,本记录仍能落库,保证可追踪恶意举报与骚扰尝试。
|
|
*
|
|
* <p>仅记录 userId、目标 ID、action、entityType、requestId 与时间等非敏感追踪字段,
|
|
* 不记录消息正文、手机号、邮箱、密码、JWT 等隐私。</p>
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class SecurityAuditService {
|
|
|
|
private final AuditLogRepository auditLogRepository;
|
|
|
|
/**
|
|
* 记录一条拒绝型安全审计事件(独立事务提交,不受外层回滚影响)。
|
|
* 持久化失败不向上抛出,避免阻断主业务异常响应路径。
|
|
*/
|
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
public void logRejected(Long userId, String action, String entityType, Long entityId,
|
|
String details, AuditContext ctx) {
|
|
try {
|
|
String safeDetails = sanitize(details);
|
|
AuditLog auditLog = AuditLog.builder()
|
|
.userId(userId)
|
|
.action(action)
|
|
.entityType(entityType)
|
|
.entityId(entityId)
|
|
.details(safeDetails)
|
|
.ipAddress(ctx != null ? ctx.getIpAddress() : null)
|
|
.userAgent(ctx != null ? ctx.getUserAgent() : null)
|
|
.requestId(ctx != null ? ctx.getRequestId() : null)
|
|
.createdAt(LocalDateTime.now())
|
|
.build();
|
|
auditLogRepository.save(auditLog);
|
|
} catch (Exception e) {
|
|
// 安全审计持久化失败不阻断主业务异常响应
|
|
log.error("Failed to persist security audit log: action={}, userId={}, error={}",
|
|
action, userId, e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 脱敏:不记录可能的敏感字段值。
|
|
*/
|
|
private String sanitize(String details) {
|
|
if (details == null) return null;
|
|
return details.replaceAll("(?i)code[=:]\\s*\\S+", "code=[REDACTED]")
|
|
.replaceAll("(?i)password[=:]\\s*\\S+", "password=[REDACTED]")
|
|
.replaceAll("(?i)token[=:]\\s*\\S+", "token=[REDACTED]")
|
|
.replaceAll("(?i)secret[=:]\\s*\\S+", "secret=[REDACTED]")
|
|
.replaceAll("(?i)phone[=:]\\s*\\S+", "phone=[REDACTED]")
|
|
.replaceAll("(?i)email[=:]\\s*\\S+", "email=[REDACTED]");
|
|
}
|
|
}
|