题目
使用反射和注解实现动态权限校验
信息
- 类型:问答
- 难度:⭐⭐
考点
自定义注解,反射应用,动态代理,权限校验
快速回答
实现步骤:
- 定义
@RequiresPermission注解标记需要权限校验的方法 - 创建动态代理拦截方法调用
- 通过反射获取方法上的注解信息
- 根据注解值执行权限校验逻辑
- 校验失败抛出安全异常
问题场景
在业务系统中,需要根据用户权限动态控制方法访问。传统硬编码方式会导致权限逻辑与业务代码耦合,使用反射和注解可以实现解耦的动态权限校验。
解决方案
1. 定义权限注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiresPermission {
String value(); // 权限标识符
}2. 创建动态代理
public class SecurityProxy implements InvocationHandler {
private final Object target;
private final User currentUser;
public SecurityProxy(Object target, User user) {
this.target = target;
this.currentUser = user;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 检查方法权限注解
if (method.isAnnotationPresent(RequiresPermission.class)) {
RequiresPermission ann = method.getAnnotation(RequiresPermission.class);
String requiredPerm = ann.value();
// 执行权限校验
if (!currentUser.hasPermission(requiredPerm)) {
throw new SecurityException("权限不足: " + requiredPerm);
}
}
return method.invoke(target, args);
}
// 创建代理对象
public static <T> T createProxy(T target, User user) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new SecurityProxy(target, user)
);
}
}3. 业务层使用示例
public interface OrderService {
@RequiresPermission("order.delete")
void deleteOrder(String orderId);
}
// 实际调用
User user = new User("test", Set.of("order.view"));
OrderService realService = new OrderServiceImpl();
OrderService securedService = SecurityProxy.createProxy(realService, user);
// 触发权限校验(将抛出SecurityException)
securedService.deleteOrder("123");核心原理
- 注解保留策略:
@Retention(RetentionPolicy.RUNTIME)确保注解在运行时可通过反射获取 - 动态代理机制:JDK动态代理在方法调用前插入权限校验逻辑
- 反射API:
Method#getAnnotation()获取方法上的注解信息
最佳实践
- 注解设计应遵循单一职责原则,只包含权限标识
- 代理对象应通过工厂方法创建,隐藏实现细节
- 权限校验失败时抛出明确异常(如
AccessDeniedException) - 结合Spring AOP可实现更优雅的解决方案
常见错误
| 错误类型 | 后果 | 解决方案 |
|---|---|---|
忘记@Retention(RUNTIME) | 运行时获取不到注解 | 明确指定注解保留策略 |
| 代理非接口方法 | JDK代理失效 | 确保目标类实现接口或改用CGLIB |
| 权限校验阻塞主逻辑 | 性能下降 | 异步校验或缓存权限结果 |
扩展知识
- Spring Security:预置
@PreAuthorize注解基于Spring EL表达式实现权限控制 - 注解继承:使用
@Inherited让子类继承父类的权限注解 - 性能优化:缓存Method-注解映射关系避免重复反射