前言
记录一下 siem_log 接口请求日志记录设计案例
一、 siem_log
DDL,Java Entity
CREATE TABLE `dem_siem_log` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '日志ID',
`username` varchar(50) DEFAULT NULL COMMENT '操作用户',
`param` mediumtext COMMENT '方法参数',
`module` varchar(64) NOT NULL,
`url` varchar(256) NOT NULL,
`event_type` varchar(32) NOT NULL,
`event_desc` varchar(256) NOT NULL,
`device_type` varchar(64) NOT NULL,
`ip` varchar(64) DEFAULT NULL COMMENT '操作者IP',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`result` varchar(128) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=238225 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("dem_siem_log")
public class SIEMLogDO {
//SIEM: 主体
private String username;
//SIEM: 主体IP
private String ip;
//SIEM: 时间
private Date createTime;
//SIEM: 模块
private String module;
//SIEM: 事件来源
@Builder.Default
private String deviceType = "web";
//SIEM: 接口路径
private String url;
//SIEM: 事件类型
private String eventType;
//SIEM: 事件描述
private String eventDesc;
//SIEM: 执行结果
private String result;
//SIEM: 原始请求参数
@Builder.Default
private String param = "";
}
二、 注解和切面
(一)注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SIEMLog {
String desc();
EventType eventType();
String[] requestMaskFields() default {};
public enum EventType {
ADD("add"), DELETE("delete"), EDIT("edit"), SEARCH("search"), VIEW("view"), LOGIN("login"), LOGOUT("logout");
@Getter
private String name;
EventType (String name) {
this.name = name;
}
}
}
(二)切面
@Slf4j
@Aspect
@Component
public class SIEMLogAspect {
private static final String MODULE = "dem-backend";
@Autowired
private SIEMLogManagerImpl siemLogManager;
@Autowired
private UserSessionHolder userSessionHolder;
@Pointcut("@annotation(com.aexpec.uds.common.annotation.SIEMLog)")
public void pointcut() {
// do nothing
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
String username = (String) userSessionHolder.getAttribute(UserSessionHolder.SESSION_USERNAME);
Object returnValue = point.proceed();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String ip = IPUtil.getIpAddress(request);
if (username == null) {
username = (String) userSessionHolder.getAttribute(UserSessionHolder.SESSION_USERNAME);
}
if (StringUtils.isBlank(username)) {
log.warn("cannot get username from session");
return returnValue;
}
MethodSignature methodSign = (MethodSignature) point.getSignature();
SIEMLog logAnnotation = methodSign.getMethod().getAnnotation(SIEMLog.class);
EventDetail.EventDetailBuilder detailBuilder = EventDetail.builder();
if (point.getArgs().length > 0) {
try {
//敏感字段掩码记录到日志中
String[] maskFields = logAnnotation.requestMaskFields();
Object arg = point.getArgs()[0];
if (maskFields.length > 0) {
Field[] declaredFields = arg.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
for (String maskField : maskFields) {
if (declaredField.getName().equals(maskField)) {
declaredField.setAccessible(true);
declaredField.set(arg, "******");
}
}
}
}
detailBuilder.input(new ObjectMapper().writeValueAsString(arg));
} catch (Exception e) {
log.warn("unable to serialize request params");
}
}
String result = "success";
if (returnValue instanceof CommonResponse) {
CommonResponse response = ((CommonResponse) returnValue);
int code = response.getCode();
if (code != ErrorCode.SUCCESS.getCode()) {
result = "failure";
}
detailBuilder.output(new ObjectMapper().writeValueAsString(response.getData()));
}
String logMsg = new ObjectMapper().writeValueAsString(detailBuilder.build());
//记录siem日志
siemLogManager.save(SIEMLogDO.builder()
.createTime(new Date())
.eventDesc(logAnnotation.desc())
.eventType(logAnnotation.eventType().getName())
.ip(ip)
.module(MODULE)
.username(username)
.url(request.getRequestURI())
.result(result)
.param(logMsg.substring(0, Math.min(logMsg.length(), 4096)))
.build());
return returnValue;
}
@Data
@Builder
public static class EventDetail {
private String input;
private String output;
}
}
(三)IPUtils
public class IPUtil {
private static final String UNKNOWN = "unknown";
protected IPUtil() {
}
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip.split(",")[0];
}
}
三、controller层使用
@PostMapping("/getFlow")
@SIEMLog(desc = "获取流程主信息", eventType = SIEMLog.EventType.SEARCH)
public CommonResponse<FlowMain> getFlow(@Valid @RequestBody GetTableFlowRequest request) {
FlowMain flowMain = approveService.getFlowMain(request.getFlowNo());
if (0 == request.getType()) {
String newTableInfo = getNewTableInfo(flowMain);
flowMain.setSubmitInfo(newTableInfo);
}
return CommonResponse.success(flowMain);
}