Java 动态代理有两种实现:
JDK 动态代理:基于接口,
java.lang.reflect.Proxy+InvocationHandlerCGLIB 动态代理:基于继承类(ASM 字节码生成),Spring AOP 默认方案之一
安全增强的核心思路:在 invoke() 拦截方法调用,在目标方法执行前后插入安全校验逻辑,不侵入原有业务代码。 常见安全能力:身份鉴权、权限校验、参数脱敏 / 校验、防重放、日志审计、加解密、黑名单拦截。
重点提醒:动态代理只是安全切面载体,不是安全算法本身;不能替代认证框架(Spring Security、Sa-Token),适合自研轻量拦截或者做框架底层封装。
一、JDK 动态代理基础 + 安全增强示例
JDK 代理要求目标对象必须实现接口。
1. 业务接口 & 实现类
public interface UserService {
void queryUser(Long userId);
void deleteUser(Long userId);
}
public class UserServiceImpl implements UserService {
@Override
public void queryUser(Long userId) {
System.out.println("查询用户:" + userId);
}
@Override
public void deleteUser(Long userId) {
System.out.println("删除用户:" + userId);
}
}
2. 安全增强 InvocationHandler(核心)
在这里统一做安全校验:权限判断、参数校验、审计日志
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Set;
public class SecurityInvocationHandler implements InvocationHandler {
// 被代理的目标对象
private final Object target;
// 当前登录用户权限集合,模拟上下文
private final Set<String> permissions;
public SecurityInvocationHandler(Object target, Set<String> permissions) {
this.target = target;
this.permissions = permissions;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ========== 安全增强点 1:权限校验 ==========
String methodName = method.getName();
if ("deleteUser".equals(methodName)) {
if (!permissions.contains("user:delete")) {
throw new SecurityException("权限不足,禁止删除用户");
}
}
// ========== 安全增强点 2:入参校验 ==========
if (args != null && args.length > 0) {
Object arg = args[0];
if (arg instanceof Long id && id <= 0) {
throw new IllegalArgumentException("非法参数:userId 不能小于等于0");
}
}
// ========== 安全增强点3:审计日志(操作留痕) ==========
System.out.printf("[安全审计] 准备调用方法:%s,参数:%s%n", methodName, args);
// 执行原始目标方法
Object result = method.invoke(target, args);
// ========== 安全增强点4:返回值脱敏(敏感字段屏蔽) ==========
// 比如返回用户信息时,手机号、身份证打码
System.out.println("[安全审计] 方法执行完成");
return result;
}
}
3. 生成代理对象 & 测试
import java.lang.reflect.Proxy;
import java.util.Set;
public class ProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 模拟当前用户只有查询权限,没有删除权限
Set<String> perm = Set.of("user:query");
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new SecurityInvocationHandler(target, perm)
);
proxy.queryUser(1001L);
// 下面这行会抛出 SecurityException
proxy.deleteUser(1001L);
}
}
二、CGLIB 动态代理(无接口类也能增强)
CGLIB 通过生成子类覆盖非 final 方法实现拦截,MethodInterceptor。
Spring 3.2+ 内置 CGLIB,也可以单独引入
cglib依赖;注意:final方法无法拦截。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Set;
public class SecurityMethodInterceptor implements MethodInterceptor {
private final Object target;
private final Set<String> permissions;
public SecurityMethodInterceptor(Object target, Set<String> permissions) {
this.target = target;
this.permissions = permissions;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 和 JDK 代理一样,在这里写安全校验逻辑
if ("deleteUser".equals(method.getName()) && !permissions.contains("user:delete")) {
throw new SecurityException("CGLIB拦截:无删除权限");
}
System.out.println("[CGLIB安全切面] 调用:" + method.getName());
return proxy.invokeSuper(obj, args);
}
// 创建代理
public static Object createProxy(Object target, Set<String> permissions) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new SecurityMethodInterceptor(target, permissions));
return enhancer.create();
}
}
三、可以用动态代理实现的安全能力清单
四、重要安全坑点(非常容易踩)
JDK 代理只拦截接口方法,类上直接定义的非接口方法无法增强;
CGLIB 不能拦截 final 类、final 方法、private 方法;
不要在代理里硬编码权限规则:建议把权限规则抽成权限管理器,支持配置化;
反射本身存在安全风险:高版本 JDK 对反射有模块限制(
--add-opens),不要信任方法参数直接反射执行;代理只是拦截,不是信任来源:鉴权信息(token、用户 ID)必须来自可信上下文,不能由前端传入后直接信任;
循环调用问题:目标方法内部 this 调用本类其他方法,不会走代理(经典坑!Spring AOP 也存在);
解决:上下文拿到代理对象
AopContext.currentProxy()调用。异常处理:如果校验失败抛出自定义
SecurityException,上层需要统一异常处理器,避免堆栈外泄。
五、和 Spring AOP 的关系
Spring AOP 底层就是封装了上面两种动态代理:
目标有接口 → JDK Proxy
无接口 → CGLIB 所以你写的
@Before、@Around安全切面本质就是动态代理的封装。 自研动态代理适合底层组件、RPC 客户端拦截;业务开发优先使用 Spring AOP + Spring Security / Sa-Token。
六、进阶扩展方向
结合注解:
@RequiresPerm("user:delete"),代理里读取方法注解自动校验权限,不用硬编码方法名;结合 ThreadLocal 存放当前登录上下文;
封装通用代理工厂,一行代码给任意对象加上安全切面;
和签名校验结合:拦截请求方法,校验请求签名,防篡改。
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问