安全|SpringAOP 实现审计日志功能

78 阅读2分钟

我们知道产品中需要审计日志,但是如何在保证产品开发的同时,在接口上快速添加上对应的审计日志。

前提回顾 产品角度:审计

image.png

  1. 用户通过统一网关访问或者操作业务A和业务B
  2. 业务A 和业务B 将操作日志打入到和统一日志平台约定的 TOPIC 上
  3. 统一日志平台消费对应的 Topic 数据,写入到对应的数据库中
  4. 审计员在统一日志平台上查看对应的数据

审计日志表结构

表字段描述
timestamp时间
ip_address用户IP 地址
user_agent客户端 UserAgent
user_id用户ID
operate操作
describe操作对象

根据日志表结构创建对应的对象

public class OperateLog {

    private long timestamp;

    private String clientIp;

    private String userAgent;

    private long userId;

    private String operate;

    private String describe;
    // getSet 方法,toString 方法
}

创建 Log 注解,为了在 controller 层上添加操作日志的注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Log {
    String operate() default "search";

    String describe() default "";
}

创建 AOP, 这里的操作日志是在访问 controller之前就进行操作,这里是将操作日志打印在控制台上,具体代码如下所示:

import com.zion.spring.demo.annotation.Log;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.zion.spring.demo.controller.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        OperateLog operateLog = createOperateLog(joinPoint);
        // 打印日志,这里可以换成写入到 Kafka 
        System.out.println(operateLog.toString());
    }

    private OperateLog createOperateLog(JoinPoint joinPoint) {
        OperateLog operateLog = new OperateLog();
        operateLog.setTimestamp(System.currentTimeMillis());

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        operateLog.setClientIp(request.getRemoteAddr());
        operateLog.setUserAgent(request.getHeader("User-Agent"));
        return appendOperate(joinPoint, operateLog);
    }

    private OperateLog appendOperate(JoinPoint joinPoint, OperateLog operateLog) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Log annotation = method.getAnnotation(Log.class);
        if (annotation != null) {
            operateLog.setOperate(annotation.operate());
            operateLog.setDescribe(annotation.describe());
        }
        return operateLog;
    }
}    

Controller 上唯一的变化就是针对每一个请求,添加对应的操作日志描述(Log 注解),如下所示:

import com.zion.spring.demo.HelloService;
import com.zion.spring.demo.MyBean;
import com.zion.spring.demo.annotation.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private HelloService service;

    @GetMapping("/message")
    @Log(describe = "获取信息")
    public String getMessage() {
        return service.getMessage();
    }
}    

总结

以上就是通过横向切面的方式,添加对应的操作日志,业务人员只需要专注在业务上的实现,而不需要关心操作日志的内容。