Spring Boot 注解实战:30 行代码搞定字段级权限控制
Spring Boot 注解实战:30 行代码搞定字段级权限控制
在企业级应用开发中,权限控制是一个绕不开的话题。从粗粒度的 URL 级权限,到细粒度的方法级权限,再到今天要聊的字段级权限,我们的安全边界在不断收紧。
字段级权限控制的需求场景其实很常见:比如同样一个用户信息接口,普通用户只能看到昵称和头像,管理员却能看到手机号和邮箱,而超级管理员还能看到更多敏感信息。
传统的做法可能是为不同角色写不同的 DTO 和接口,这种方式不仅冗余,还难以维护。今天我要分享的是如何利用 Spring Boot 的注解特性,用更优雅的方式实现字段级权限控制。
实现思路
我们的核心思路是在序列化阶段对响应数据进行拦截,根据当前用户的角色决定哪些字段可以被序列化并返回给前端。主要涉及三个关键组件:
- 自定义注解:标记需要进行权限控制的字段及所需角色
- AOP 切面:在接口返回前对结果进行处理
- 序列化过滤器:根据权限动态过滤字段
代码实现
首先,我们需要定义一个用于标记字段权限的注解:
java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldPermission {
// 允许访问的角色列表
String[] roles() default {};
// 当没有权限时显示的默认值
String defaultValue() default "*****";
}
接下来,创建一个 AOP 切面,用于在控制器方法返回结果后进行处理:
java
@Aspect
@Component
public class FieldPermissionAspect {
@Autowired
private UserContext userContext; // 自定义用户上下文,用于获取当前用户角色
@AfterReturning(pointcut = "@annotation(org.springframework.web.bind.annotation.RequestMapping)",
returning = "result")
public Object handleFieldPermission(Object result) {
if (result == null) return null;
// 获取当前用户角色
String currentRole = userContext.getCurrentUserRole();
// 使用自定义序列化器处理结果
return FieldPermissionSerializer.serialize(result, currentRole);
}
}
然后是核心的序列化过滤器实现:
java
public class FieldPermissionSerializer {
public static Object serialize(Object obj, String currentRole) {
if (obj == null) return null;
ObjectMapper objectMapper = new ObjectMapper();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept(
new HashSet<>(getAllowedFields(obj.getClass(), currentRole))
);
objectMapper.addMixIn(obj.getClass(), getFilterMixIn(obj.getClass(), filter));
try {
String json = objectMapper.writeValueAsString(obj);
return objectMapper.readValue(json, obj.getClass());
} catch (Exception e) {
throw new RuntimeException("字段权限过滤失败", e);
}
}
private static List<String> getAllowedFields(Class<?> clazz, String currentRole) {
List<String> allowedFields = new ArrayList<>();
// 遍历所有字段,检查权限
for (Field field : clazz.getDeclaredFields()) {
FieldPermission permission = field.getAnnotation(FieldPermission.class);
if (permission == null) {
// 没有注解的字段默认允许访问
allowedFields.add(field.getName());
} else {
// 检查当前角色是否有权限访问该字段
if (Arrays.asList(permission.roles()).contains(currentRole)) {
allowedFields.add(field.getName());
}
}
}
return allowedFields;
}
// 创建动态混入类用于过滤
private static Class<?> getFilterMixIn(Class<?> clazz, SimpleBeanPropertyFilter filter) {
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter(clazz.getName(), filter);
// 具体实现略...
}
}
最后,我们就可以在实体类中使用这个注解了:
java
public class User {
private Long id;
private String username;
private String nickname;
@FieldPermission(roles = {"ADMIN", "SUPER_ADMIN"}, defaultValue = "*****")
private String email;
@FieldPermission(roles = {"SUPER_ADMIN"}, defaultValue = "*****")
private String phone;
// getter和setter...
}
优化与扩展
上面的实现提供了一个基础框架,在实际项目中我们还可以进行一些优化:
- 缓存字段权限信息,避免每次序列化都反射解析类
- 支持更复杂的权限表达式,而不仅仅是角色列表
- 对集合类型进行处理,确保列表中的每个对象都能正确过滤
- 结合 Spring Security,更紧密地集成到安全框架中
总结
通过注解 + AOP + 序列化过滤的组合,我们实现了一个轻量级但功能强大的字段级权限控制方案。这种方式的优点在于:
- 侵入性低,只需在字段上添加注解
- 灵活性高,可根据实际需求定制权限规则
- 可维护性好,权限配置集中在注解上
这种思路不仅适用于权限控制,还可以扩展到数据脱敏、日志过滤等场景。希望今天的分享能给大家带来一些启发,欢迎在评论区交流你的想法!
感谢关注【AI码力】获取更多Java秘籍!