Spring Boot 注解实战:30 行代码搞定字段级权限控制

Spring Boot 注解实战:30 行代码搞定字段级权限控制

Spring Boot 注解实战:30 行代码搞定字段级权限控制

在企业级应用开发中,权限控制是一个绕不开的话题。从粗粒度的 URL 级权限,到细粒度的方法级权限,再到今天要聊的字段级权限,我们的安全边界在不断收紧。

字段级权限控制的需求场景实则很常见:列如同样一个用户信息接口,普通用户只能看到昵称和头像,管理员却能看到手机号和邮箱,而超级管理员还能看到更多敏感信息。

传统的做法可能是为不同角色写不同的 DTO 和接口,这种方式不仅冗余,还难以维护。今天我要分享的是如何利用 Spring Boot 的注解特性,用更优雅的方式实现字段级权限控制。

实现思路

我们的核心思路是在序列化阶段对响应数据进行拦截,根据当前用户的角色决定哪些字段可以被序列化并返回给前端。主要涉及三个关键组件:

  1. 自定义注解:标记需要进行权限控制的字段及所需角色
  2. AOP 切面:在接口返回前对结果进行处理
  3. 序列化过滤器:根据权限动态过滤字段

代码实现

第一,我们需要定义一个用于标记字段权限的注解:

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...
}

优化与扩展

上面的实现提供了一个基础框架,在实际项目中我们还可以进行一些优化:

  1. 缓存字段权限信息,避免每次序列化都反射解析类
  2. 支持更复杂的权限表达式,而不仅仅是角色列表
  3. 对集合类型进行处理,确保列表中的每个对象都能正确过滤
  4. 结合 Spring Security,更紧密地集成到安全框架中

总结

通过注解 + AOP + 序列化过滤的组合,我们实现了一个轻量级但功能强劲的字段级权限控制方案。这种方式的优点在于:

  • 侵入性低,只需在字段上添加注解
  • 灵活性高,可根据实际需求定制权限规则
  • 可维护性好,权限配置聚焦在注解上

这种思路不仅适用于权限控制,还可以扩展到数据脱敏、日志过滤等场景。希望今天的分享能给大家带来一些启发,欢迎在评论区交流你的想法!


感谢关注【AI码力】获取更多Java秘籍!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
山猫的头像 - 鹿快
评论 共3条

请登录后发表评论