1 Star 0 Fork 0

zarchary / LogAuit

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 7.53 KB
一键复制 编辑 原始数据 按行查看 历史
zarchary 提交于 2024-03-26 16:19 . :rocket:日记审计 :rocket:

java 日志审计

一、引言 阐述日志审计在保证系统安全、追踪操作行为、满足合规要求等方面的关键作用,并指出为何选用SLF4J作为统一日志接口,以及 Aspect作为具体的日志实现框架来完成日志审计功能。

模块介绍

  • demo-a 为模块A。简单的controller,全局拦截器GlobalInterctor 记录日志,拦截器配置CustomWebConfig
  • demo-b 为模块B。 简单的controller,全局拦截器GlobalInterctor 记录日志,拦截器配置CustomWebConfig
  • demo-log 为日志模块 公共模块 切面操作

附上对应demo 地址:

二、SLF4J与Aspect简介

  • SLF4J的核心价值及其适配多种日志框架的能力
  • Aspect 来作为无侵入拦截

三、利用Aspect实现日志审计功能

* 日志切面类上应有@Aspect注解。
* 切面方法的切入点表达式应能匹配到模块A和B中的Controller方法,例如:Java
@Pointcut*("execution (* com.demo.demob.controller..*.*(..)) || " +
        "execution (* com.demo.demoa.controller..*.*(..)) ||" +
        "@annotation(com.demo.log.OperLog)")
* 确保公共模块作为依赖被正确地引入到模块A和B中。

1 启用Spring AOP代理: 在模块A和B的Spring Boot应用主类上添加@EnableAspectJAutoProxy注解来启用AOP代理。

2 确保拦截器工作正常

  • 拦截器类应正确地注册到Spring容器中,例如使用@Component注解,并在配置类中通过@Bean方法暴露出去,或者实现WebMvcConfigurer接口重写addInterceptors方法添加拦截器。

3 在线程局部存储中传递日志信息

  • 在切面中,将日志信息存入ThreadLocal变量。
  • 在拦截器的后置处理方法中,从ThreadLocal中取出并处理日志信息。

四、核心代码

1.切面注解

  • 通过注解形式添加到需要记录的方法上,即可完成对此方法的监控
package com.demo.log;

import java.lang.annotation.*;

/**
 * @author zzz
 * @pack_name com.mw.core.log
 * @project_name mcloud
 * @date 2024/3/25 11:03:40
 * @Description 注解操作
 */
@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {
    String operModul() default ""; // 操作模块
    String operType() default "";  // 操作类型
    String operDesc() default "";  // 操作说明
}

例如

在controller中 ,使用 @OperLog 记录操作的模块,内容等记录

@GetMapping("/test")
    @OperLog(operType = "测试", operDesc = "切面内容测试a")
    public String test() {
       return "HelloController.test------a";
    }

@Pointcut注解用于定义一个切入点(Pointcut)

  • @annotation 填入注解所在包
 @Pointcut("@annotation(com.demo.log.OperLog)")
    public void operLogPointCut() {
    }

@AfterReturning 通知 pointcut 填写切入

@AfterReturning(pointcut = "operLogPointCut()", returning = "keys")
    public JSONObject saveOperLog(JoinPoint joinPoint, Object keys) {
		// 具体增强内容。包含日志等。 具体代码参照 demo-log包内OperLogAspect类
		//伪代码
		// 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        // 请求路径
        String requestURI = request.getRequestURI();
        // 请求路径
        logEntity.setFUrl(requestURI);
        // 主机地址
        String host = request.getRemoteAddr();

		// 获取日志注解的@OperLog的内容
		 // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作 添加在controller上的@OperLog 
        OperLog opLog = method.getAnnotation(OperLog.class);
        logEntity.setFTitle(opLog.operDesc());

		// 当然,如果项目使用了 swagger或者增强knife4j-openapi3-spring-boot-starter
		//通过 反射获取 接口文档提供的注解 。获取接口文档里面的内容。获取 @ApiMethod 中的内容。完善日志记录内容
		/**
		 *   @ApiMethod(desc = "审批回调相关业务接口:", 
		 * 	 outerPath = "/{groupId}/callback", 	
		 *   innerPath = "/{groupId}/callback")

		 */
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
		ApiOperation annotation = method.getAnnotation(ApiOperation.class);

		// 最后 将处理内容设置到 本地线程上线文中
		ThreadUitl.set("log",logEntity )
	}

具体代码参照 demo-log包内OperLogAspect类

2.日记记录

  • 创建全局拦截器
  • 拦截器后置处理记录产生的日志

模块 demo-a 实现拦截器

package com.demo.demoa.interctor;

import com.alibaba.fastjson2.JSONObject;
import com.demo.util.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * @author zzz
 * @pack_name com.demo.demoa.interctor
 * @project_name Log-Auit
 * @date 2024/3/26 14:03:54
 * @Description 全局拦截器
 */
@Component
@Slf4j
public class GlobalInterctor  implements HandlerInterceptor {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        /**
         * demo A 日记记录。
         * 因为是跨应用获取日志。这里采用本地线程获取上下文  通过ThreadLocalUtil.get("log")获取
         */
        JSONObject logEntity = (JSONObject)ThreadLocalUtil.get("log");
        if (logEntity !=null){
            // 可以记录进数据库
            log.info("日志记录-===========》:{}",logEntity.toJSONString());
        }


        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

注册拦截器

package com.demo.demoa.config;

import com.demo.demoa.interctor.GlobalInterctor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author zzz
 * @pack_name com.demo.demoa
 * @project_name Log-Auit
 * @date 2024/3/26 15:03:03
 * @Description
 */
@Configuration
@RequiredArgsConstructor
public class CustomWebConfig implements WebMvcConfigurer {
    private final GlobalInterctor globalInterctor;



    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(globalInterctor).addPathPatterns("/**");
    }
}

模块B类似。 只需要保证在切面类中 能对B模块需要 记录日记的包进行切入即可

例如:

  • 需要拦截多个模块, 就需要 @execution切入点对应想要记录的哪个包下面的内容。如果我想要 模块A和模块B都想切入,那么使用 || 分割即可
@Pointcut("execution (* com.demo.demob.controller..*.*(..)) || " +
            "execution (* com.demo.demoa.controller..*.*(..))
    public void operLogPointCut() {
    }
1
https://gitee.com/zarchary/log-auit.git
git@gitee.com:zarchary/log-auit.git
zarchary
log-auit
LogAuit
master

搜索帮助