Skip to content

Commit 0c5e019

Browse files
authored
Merge pull request #22 from ReLive27/main
feat(audit): add audit log handler and AuditEventRepository abstraction
2 parents 0ea5fb8 + 566f352 commit 0c5e019

19 files changed

Lines changed: 501 additions & 15 deletions

File tree

audit/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ jar {
66

77
dependencies {
88
implementation libraries.springBootStarterAop
9+
implementation libraries.springBootStarterWeb
10+
implementation libraries.userAgentUtils
11+
implementation libraries.reactor
912
implementation project(':simpleauth0-core')
1013
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package io.github.simpleauth0.audit;
2+
3+
import io.github.simpleauth0.audit.annotation.AuditLog;
4+
import io.github.simpleauth0.audit.expression.ExpressionAttribute;
5+
import io.github.simpleauth0.audit.handler.AuditLogStorageHandler;
6+
import io.github.simpleauth0.core.utils.AnnotationUtils;
7+
import org.aopalliance.aop.Advice;
8+
import org.aopalliance.intercept.MethodInterceptor;
9+
import org.aopalliance.intercept.MethodInvocation;
10+
import org.springframework.aop.Pointcut;
11+
import org.springframework.aop.PointcutAdvisor;
12+
import org.springframework.aop.framework.AopInfrastructureBean;
13+
import org.springframework.aop.support.AopUtils;
14+
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
15+
import org.springframework.core.Ordered;
16+
import org.springframework.core.ParameterNameDiscoverer;
17+
import org.springframework.expression.Expression;
18+
import org.springframework.expression.ExpressionParser;
19+
import org.springframework.expression.spel.standard.SpelExpressionParser;
20+
import org.springframework.lang.NonNull;
21+
import org.springframework.util.Assert;
22+
23+
import java.lang.reflect.Method;
24+
25+
/**
26+
* @author: ReLive27
27+
* @date: 2025/4/25 22:54
28+
*/
29+
public class AuditLogMethodInterceptor implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
30+
31+
private final AuditLogExpressionAttributeRegistry registry = new AuditLogExpressionAttributeRegistry();
32+
33+
private int order = 100;
34+
35+
private final Pointcut pointcut;
36+
37+
private AuditLogStorageHandler auditLogStorageHandler;
38+
39+
public AuditLogMethodInterceptor(AuditLogStorageHandler<? extends ExpressionAttribute> auditLogStorageHandler) {
40+
Assert.notNull(auditLogStorageHandler, "auditLogStorageHandler cannot be null");
41+
this.auditLogStorageHandler = auditLogStorageHandler;
42+
this.pointcut = AuditLogMethodPointcuts.forAnnotations(AuditLog.class);
43+
}
44+
45+
private ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
46+
47+
48+
@Override
49+
public Object invoke(MethodInvocation mi) throws Throwable {
50+
Object returnedObject = null;
51+
Boolean success = true;
52+
try {
53+
returnedObject = mi.proceed();
54+
return returnedObject;
55+
} catch (Exception e) {
56+
returnedObject = e.getMessage();
57+
success = false;
58+
throw e;
59+
} finally {
60+
AuditLogExpressionAttribute attribute = this.registry.getAttribute(mi);
61+
if (attribute != ExpressionAttribute.NULL_ATTRIBUTE) {
62+
String[] parameterNames = discoverer.getParameterNames(mi.getMethod());
63+
if (parameterNames != null && parameterNames.length > 0) {
64+
attribute.setParams(parameterNames);
65+
attribute.setArgs(mi.getArguments());
66+
}
67+
attribute.setResult(returnedObject);
68+
attribute.setSuccess(success);
69+
this.auditLogStorageHandler.handler(attribute);
70+
}
71+
}
72+
73+
}
74+
75+
@Override
76+
public Pointcut getPointcut() {
77+
return this.pointcut;
78+
}
79+
80+
@Override
81+
public Advice getAdvice() {
82+
return this;
83+
}
84+
85+
@Override
86+
public boolean isPerInstance() {
87+
return true;
88+
}
89+
90+
@Override
91+
public int getOrder() {
92+
return this.order;
93+
}
94+
95+
public void setOrder(int ordering) {
96+
this.order = ordering;
97+
}
98+
99+
public void setAuditLogStorageHandler(AuditLogStorageHandler auditLogStorageHandler) {
100+
this.auditLogStorageHandler = auditLogStorageHandler;
101+
}
102+
103+
private final class AuditLogExpressionAttributeRegistry {
104+
105+
private final ExpressionParser parser = new SpelExpressionParser();
106+
107+
public final AuditLogExpressionAttribute getAttribute(MethodInvocation mi) {
108+
Method method = mi.getMethod();
109+
Object target = mi.getThis();
110+
Class<?> targetClass = (target != null) ? target.getClass() : null;
111+
return resolveAttribute(method, targetClass);
112+
}
113+
114+
@NonNull
115+
AuditLogExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
116+
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
117+
AuditLog auditLog = findPostFilterAnnotation(specificMethod);
118+
if (auditLog == null) {
119+
return AuditLogExpressionAttribute.NULL_ATTRIBUTE;
120+
}
121+
Expression operatorExpression = parser.parseExpression(auditLog.operator());
122+
return new AuditLogExpressionAttribute(operatorExpression, auditLog);
123+
}
124+
125+
private AuditLog findPostFilterAnnotation(Method method) {
126+
AuditLog auditLog = AnnotationUtils.findUniqueAnnotation(method, AuditLog.class);
127+
return (auditLog != null) ? auditLog
128+
: AnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), AuditLog.class);
129+
}
130+
131+
}
132+
133+
public static final class AuditLogExpressionAttribute extends ExpressionAttribute {
134+
135+
private static final AuditLogExpressionAttribute NULL_ATTRIBUTE = new AuditLogExpressionAttribute(null, null);
136+
137+
private final AuditLog auditLog;
138+
139+
private Object result;
140+
141+
private Boolean success;
142+
143+
private AuditLogExpressionAttribute(Expression expression, AuditLog auditLog) {
144+
super(expression);
145+
this.auditLog = auditLog;
146+
}
147+
148+
public void setResult(Object result) {
149+
this.result = result;
150+
}
151+
152+
public void setSuccess(Boolean success) {
153+
this.success = success;
154+
}
155+
156+
public Object getResult() {
157+
return result;
158+
}
159+
160+
public Boolean getSuccess() {
161+
return success;
162+
}
163+
164+
public AuditLog getAuditLog() {
165+
return auditLog;
166+
}
167+
}
168+
}
169+
170+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.simpleauth0.audit;
2+
3+
import org.springframework.aop.Pointcut;
4+
import org.springframework.aop.support.ComposablePointcut;
5+
import org.springframework.aop.support.Pointcuts;
6+
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
7+
8+
import java.lang.annotation.Annotation;
9+
10+
/**
11+
* @author: ReLive27
12+
* @date: 2025/4/26 22:45
13+
*/
14+
public class AuditLogMethodPointcuts {
15+
@SafeVarargs
16+
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
17+
ComposablePointcut pointcut = null;
18+
for (Class<? extends Annotation> annotation : annotations) {
19+
if (pointcut == null) {
20+
pointcut = new ComposablePointcut(classOrMethod(annotation));
21+
}
22+
else {
23+
pointcut.union(classOrMethod(annotation));
24+
}
25+
}
26+
return pointcut;
27+
}
28+
29+
private static Pointcut classOrMethod(Class<? extends Annotation> annotation) {
30+
return Pointcuts.union(new AnnotationMatchingPointcut(null, annotation, true),
31+
new AnnotationMatchingPointcut(annotation, true));
32+
}
33+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.github.simpleauth0.audit.annotation;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* @author: ReLive27
7+
* @date: 2025/4/26 22:46
8+
*/
9+
@Target(ElementType.METHOD)
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Documented
12+
public @interface AuditLog {
13+
/**
14+
* 操作名称,比如 "用户登录"、"创建角色",支持 SpEL 表达式。
15+
*/
16+
String actionName();
17+
18+
/**
19+
* 资源类型,比如 "用户"、"角色"、"菜单"、"接口"。
20+
*/
21+
String resourceType();
22+
23+
/**
24+
* 是否记录请求参数。
25+
*/
26+
boolean recordRequest() default false;
27+
28+
/**
29+
* 是否记录响应结果。
30+
*/
31+
boolean recordResponse() default true;
32+
33+
/**
34+
* 当前操作用户,支持 SpEL 表达式,比如 "#{#user.username}" 或 "#{@securityContext.getCurrentUser()}"。
35+
*/
36+
String operator() default "";
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.github.simpleauth0.audit.configuration;
2+
3+
import io.github.simpleauth0.audit.AuditLogMethodInterceptor;
4+
import io.github.simpleauth0.audit.handler.AuditLogStorageHandler;
5+
import io.github.simpleauth0.audit.handler.DefaultAuditLogStorageHandler;
6+
import io.github.simpleauth0.audit.repository.AuditLogEventRepository;
7+
import org.springframework.aop.Advisor;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.context.annotation.Configuration;
11+
12+
/**
13+
* @author: ReLive27
14+
* @date: 2025/4/26 22:32
15+
*/
16+
@Configuration(proxyBeanMethods = false)
17+
public class AuditLogMethodConfiguration {
18+
19+
@Bean("auditLogAdvisor")
20+
@ConditionalOnMissingBean(name = "auditLogAdvisor")
21+
Advisor auditLogAdvisor(AuditLogStorageHandler auditLogStorageHandler) {
22+
return new AuditLogMethodInterceptor(auditLogStorageHandler);
23+
}
24+
25+
@Bean
26+
@ConditionalOnMissingBean(AuditLogStorageHandler.class)
27+
AuditLogStorageHandler auditLogStorageHandler(AuditLogEventRepository auditLogRepository) {
28+
return new DefaultAuditLogStorageHandler(auditLogRepository);
29+
}
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.simpleauth0.audit.configuration;
2+
3+
import org.springframework.context.annotation.ImportSelector;
4+
import org.springframework.core.type.AnnotationMetadata;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
/**
10+
* @author: ReLive27
11+
* @date: 2025/4/26 22:29
12+
*/
13+
public class AuditLogMethodSelector implements ImportSelector {
14+
@Override
15+
public String[] selectImports(AnnotationMetadata importMetadata) {
16+
if (!importMetadata.hasAnnotation(EnableAuditLog.class.getName())) {
17+
return new String[0];
18+
}
19+
EnableAuditLog annotation = importMetadata.getAnnotations().get(EnableAuditLog.class).synthesize();
20+
List<String> imports = new ArrayList<>();
21+
if (annotation.method()) {
22+
imports.add(AuditLogMethodConfiguration.class.getName());
23+
}
24+
25+
return imports.toArray(new String[0]);
26+
}
27+
28+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.github.simpleauth0.audit.configuration;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.context.annotation.Import;
5+
6+
import java.lang.annotation.*;
7+
8+
/**
9+
* @author: ReLive27
10+
* @date: 2025/4/25 23:20
11+
*/
12+
@Retention(RetentionPolicy.RUNTIME)
13+
@Target(ElementType.TYPE)
14+
@Documented
15+
@Import(AuditLogMethodSelector.class)
16+
@Configuration
17+
public @interface EnableAuditLog {
18+
19+
boolean method() default true;
20+
}

core/src/main/java/io/github/simpleauth0/core/expression/ExpressionAttribute.java renamed to audit/src/main/java/io/github/simpleauth0/audit/expression/ExpressionAttribute.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.simpleauth0.core.expression;
1+
package io.github.simpleauth0.audit.expression;
22

33
import lombok.Data;
44
import org.springframework.expression.Expression;
@@ -14,7 +14,19 @@ public class ExpressionAttribute {
1414

1515
private final Expression expression;
1616

17+
private Object[] args;
18+
19+
private String[] params;
20+
1721
public ExpressionAttribute(Expression expression) {
1822
this.expression = expression;
1923
}
24+
25+
public void setArgs(Object[] args) {
26+
this.args = args;
27+
}
28+
29+
public void setParams(String[] params) {
30+
this.params = params;
31+
}
2032
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.github.simpleauth0.audit.handler;
2+
3+
import io.github.simpleauth0.audit.expression.ExpressionAttribute;
4+
5+
/**
6+
* @author: ReLive27
7+
* @date: 2025/5/23 22:06
8+
*/
9+
public interface AuditLogStorageHandler<T extends ExpressionAttribute> {
10+
11+
void handler(T expressionAttribute);
12+
}

0 commit comments

Comments
 (0)