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

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]");
}
}