审计日志
使用spring AOP实现的审计日志
环境准备
jdk1.8,springboot
依赖如下:
<!-- 添加aspectj -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
项目结构
我写这个项目时是后期加入,导致多个服务需要用,所以将相关的都存入一个包中
自定义审计注解
package com.bocloud.devops.accesslog;
import com.paas.bocloud.accesslog.InterfaceType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface Audit {
//需要得到什么信息,就定义什么,看自己需求
/**
* 模块类
*/
Module module();
/**
* 模块名称
*/
String object();
/**
* 操作
*/
String operation();
/**
* 操作类型
*/
InterfaceType type();
//BoCloudFunction function() default BoCloudFunction.NULL;
String detail() default "";
/**
* 扩展信息
*/
Class<?>[] entity() default {};
}
定义切面类
package com.bocloud.devops.accesslog;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.paas.bocloud.common.http.response.DataResponse;
import com.paas.bocloud.common.jwt.util.BaseContextHandler;
import net.sf.json.JSONArray;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
@Aspect
@Component
public class AuditAop {
private static final Logger LOGGER = LoggerFactory.getLogger(AuditAop.class);
@Autowired
private RabbitmqClient rabbitmqClient;
@Autowired
private PlatFormFeignClient platFormFeignClient;
// 操作发起者
ThreadLocal<Integer> userId = new ThreadLocal<>();
// 操作租户ID
ThreadLocal<Integer> tenantId = new ThreadLocal<>();
// 功能模块
ThreadLocal<String> moudleCode = new ThreadLocal<>();
// 操作类型
ThreadLocal<Integer> operateType = new ThreadLocal<>();
// IP地址
ThreadLocal<String> ip = new ThreadLocal<>();
// 操作时间点
ThreadLocal<String> operateTime = new ThreadLocal<>();
// 操作信息实体
ThreadLocal<AccessLog> AccessLog = new ThreadLocal<>();
// 对CIS,额外的菜单id信息extension
ThreadLocal<String> extension = new ThreadLocal<>();
ThreadLocal<Long> startTime = new ThreadLocal();
// 声明AOP切入点
@Pointcut("@annotation(com.bocloud.devops.accesslog.Audit)")
public void audit() {
System.out.println(1);
}
@Before("audit()")
public void beforeExec(JoinPoint joinPoint) {
}
@After("audit()")
public void afterExec(JoinPoint joinPoint) {
}
@Around("audit()")
public Object aroundExec(ProceedingJoinPoint pjp) throws Throwable {
//开始时间
startTime.set(System.currentTimeMillis());
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
extension.set(request.getParameter("extension"));
operateTime.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//获得requestIp(请求IP)
ip.set(IpUtil.getIpAddress(request));
MethodSignature ms = (MethodSignature) pjp.getSignature();
Method method = ms.getMethod();
// 获取注解的参数信息
Audit auditAnno = method.getAnnotation(Audit.class);
moudleCode.set(auditAnno.module().getServiceName());
operateType.set(auditAnno.type().getIndex());
//为实体类赋值,以便存入数据库
AccessLog accessLog=new AccessLog();
//获取操作用户 (需判断userId是否为空,是否登录过期)
if (BaseContextHandler.getUserId()!=null){
accessLog.setUserId(Integer.valueOf(BaseContextHandler.getUserId()));
accessLog.setUserName(BaseContextHandler.getUserName());
}
accessLog.setModule(moudleCode.get());//获得模块名
accessLog.setAction(auditAnno.operation());//获得操作
//获得参数
Object[] args = pjp.getArgs();
accessLog.setDetail(JSON.toJSONString(args));
accessLog.setGmtCreate(new Date());//获得操作日期
accessLog.setObject(auditAnno.object());
accessLog.setRequestUrl(request.getRequestURI());//访问地址
accessLog.setInterfaceType(operateType.get());
accessLog.setRequestIp(ip.get());
accessLog.setResponseIp(IpUtil.getLocalIp());//responseIp(响应IP)
//获得此接口的响应结果 需存库写入,不需要最好不要写,在最后return
//pjp.proceed()获得真正被代理对象的业务方法的返回值
Object rtn = pjp.proceed();
//获得耗时
accessLog.setCost(System.currentTimeMillis() - startTime.get());
startTime.remove();
//获得返回信息
accessLog.setResult(JSON.toJSONString(rtn));
AccessLog.set(accessLog);
return rtn;
}
/**
* 带参返回
*/
@AfterReturning(pointcut = "audit()")
public void doAfterReturning() {
try {
// 返回码SUCCESS才去审计记录
if (ResponseContent.RESPCODE_SUCCESS.equals(rc.getRespCode())) {
if (null == msg.get() && extension.get() != null) {
LOGGER.warn("Warning occured, because msg is null!");
} else if (!(operateType.get() == Operate.FETCH_DATA
&& (extension.get() == null || extension.get().isEmpty()))) {
//添加到数据库
auditService.insert(AccessLog.get());
}
}
} catch (Exception e) {
LOGGER.warn("Warning occured while afterReturning, cause by: ", e);
}
}
/**
* 不带参返回
*/
@AfterReturning(pointcut = "audit()")
public void doAfterReturning(JoinPoint joinPoint) {
LOGGER.debug("afterReturning without returnType..");
}
@AfterThrowing(pointcut = "audit()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
LOGGER.error("Error occured, cause by {}", e.getMessage());
}
获得请求IP,响应Ip工具类 (可以按需求索取)
package com.bocloud.devops.accesslog;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
public class IpUtil {
/**
* 获取本地IP地址
*
*/
public static String getLocalIp() {
if (isWindowsOS()) {
String hostAddress = null;
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
}catch (UnknownHostException e){
// 获取windows ip 地址发送异常
}
return hostAddress;
} else {
return getLinuxLocalIp();
}
}
/**
* 判断操作系统是否是Windows
*
* @return
*/
public static boolean isWindowsOS() {
String windowsOsName = "windows";
boolean isWindowsOs = false;
String osName = System.getProperty("os.name");
if (osName.toLowerCase().contains(windowsOsName)) {
isWindowsOs = true;
}
return isWindowsOs;
}
/**
* 获取本地Host名称
*/
public static String getLocalHostName() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
/**
* 获取Linux下的IP地址
*
* @return IP地址
* @throws SocketException
*/
private static String getLinuxLocalIp(){
String ip = "";
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
String name = intf.getName();
if (!name.contains("docker") && !name.contains("lo")) {
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
String ipaddress = inetAddress.getHostAddress().toString();
if (!ipaddress.contains("::") && !ipaddress.contains("0:0:") && !ipaddress.contains("fe80")) {
ip = ipaddress;
}
}
}
}
}
} catch (SocketException ex) {
ip = "127.0.0.1";
}
return ip;
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
*
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
*
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
* 192.168.1.100
*
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String unknownIpName = "unknown";
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || unknownIpName.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unknownIpName.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unknownIpName.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || unknownIpName.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || unknownIpName.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
相关枚举类
只是举例,按实际需求定义
package com.bocloud.devops.accesslog;
import lombok.Getter;
/**
* 各个模块的consul请求路径和api路径
*/
@Getter
public enum Module {
Workflow("devopsworkflowapi", "devops-workflow"),
WorkflowEngine("devopsworkflowengineapi", "devops-workflow-engine"),
Platform("devopsplatformapi", "devops-platform"),
Jira("devopsjiraapi", "devops-jira"),
Boclink("devopsboclinkapi", "devops-message"),
Pipeline("devopspipelineapi","devops-pipeline"),
Upms("upmsapi", "paas-basic-upms"),
Runtime("runtimeapi", "paas-runtime"),
Application("applicationapi", "paas-application");
private Module(String gateWay, String serviceName) {
this.gateWay = gateWay;
this.serviceName = serviceName;
}
private String gateWay;
private String serviceName;
}
package com.paas.bocloud.accesslog;
import lombok.Getter;
@Getter
public enum InterfaceType {
INSERT("inster", 1, "新增接口"),
DELETE("delete", 2, "删除接口"),
UPDATE("update", 3, "修改接口"),
SELECT("select", 4, "查询接口");
private String interfaceType;
private int index;
private String name;
private InterfaceType(String interfaceType, int index, String name) {
this.interfaceType = interfaceType;
this.index = index;
this.name = name;
}
}
定义审计信息实体类
package com.bocloud.devops.accesslog;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class AccessLog implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 操作 中文描述
*/
private String action;
/**
* 耗时
*/
private Long cost;
/**
* 请求参数
*/
private String detail;
/**
* 操作日期
*/
private Date gmtCreate;
/**
* 服务 例如:服务名
*/
private String module;
private String object; // 接口模块
/**
* 请求URL
*/
private String requestUrl;
/**
* 请求IP
*/
private String requestIp;
/**
* 响应IP
*/
private String responseIp;
private String result;
/**
* 接口类型 1新增类接口 2删除类接口 3修改类接口 4查询类接口
*/
private Integer interfaceType;
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名
*/
private String userName;
}
举例:
public class controller{
@RequestMapping(value = "/ceshi", method = { RequestMethod.GET})
@Audit(module = Module.Pipeline,object ="审计日志" ,operation = "测试审计日志",type = InterfaceType.SLECT)
public DataResponse<Object> ceshi(){
return DataResponse.success("审计日志测试");
}
}