小易说IT 小易说IT

Java 动态代理增强安全性代码示例

Java 动态代理有两种实现:

  1. JDK 动态代理:基于接口,java.lang.reflect.Proxy + InvocationHandler

  2. CGLIB 动态代理:基于继承类(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();
    }
}

三、可以用动态代理实现的安全能力清单

安全能力

实现位置

说明

接口权限控制

invoke 前置

校验角色 / 权限码,拒绝无权限调用

身份认证 token 校验

invoke 前置

从上下文拿 token 解析,过期直接拦截

请求参数校验 & 防注入

invoke 前置

参数非空、长度、正则,过滤危险字符

防重放攻击

invoke 前置

校验请求流水号 + 时间戳,结合本地缓存

敏感参数加解密

invoke 前后

入参自动解密、出参自动加密

返回数据脱敏

invoke 后置

手机号、身份证、银行卡掩码

安全审计日志

前后都可

记录调用人、方法、参数、结果、异常

限流熔断

前置

基于接口粒度限流,防止暴力调用

黑名单拦截

前置

拦截黑名单账号 / IP

异常安全包装

catch 块

原始异常堆栈不对外暴露,防止信息泄露

四、重要安全坑点(非常容易踩)

  1. JDK 代理只拦截接口方法,类上直接定义的非接口方法无法增强;

  2. CGLIB 不能拦截 final 类、final 方法、private 方法;

  3. 不要在代理里硬编码权限规则:建议把权限规则抽成权限管理器,支持配置化;

  4. 反射本身存在安全风险:高版本 JDK 对反射有模块限制(--add-opens),不要信任方法参数直接反射执行;

  5. 代理只是拦截,不是信任来源:鉴权信息(token、用户 ID)必须来自可信上下文,不能由前端传入后直接信任;

  6. 循环调用问题:目标方法内部 this 调用本类其他方法,不会走代理(经典坑!Spring AOP 也存在);

    解决:上下文拿到代理对象 AopContext.currentProxy() 调用。

  7. 异常处理:如果校验失败抛出自定义 SecurityException,上层需要统一异常处理器,避免堆栈外泄。

五、和 Spring AOP 的关系

Spring AOP 底层就是封装了上面两种动态代理:

  • 目标有接口 → JDK Proxy

  • 无接口 → CGLIB 所以你写的 @Before、@Around 安全切面本质就是动态代理的封装。 自研动态代理适合底层组件、RPC 客户端拦截;业务开发优先使用 Spring AOP + Spring Security / Sa-Token。

六、进阶扩展方向

  1. 结合注解:@RequiresPerm("user:delete"),代理里读取方法注解自动校验权限,不用硬编码方法名;

  2. 结合 ThreadLocal 存放当前登录上下文;

  3. 封装通用代理工厂,一行代码给任意对象加上安全切面;

  4. 和签名校验结合:拦截请求方法,校验请求签名,防篡改。


本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/java-dynamic-proxy-security-enhancement-code-example

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/