1. 概述

在本篇教程中,我们将展示如何根据 Spring Security 中定义的用户角色,动态地过滤 JSON 序列化的输出内容。

2. 为什么需要过滤?

考虑一个典型的使用场景:我们的 Web 应用面向不同角色的用户,比如 UserAdmin

需求很简单也很常见:

  • 管理员(Admin) 可以访问通过 REST API 暴露的对象的所有内部状态。
  • 普通用户(User) 只能看到预定义的一组属性。

我们使用 Spring Security 来控制对资源的访问权限。现在问题来了:如何在同一个对象上根据不同角色返回不同的 JSON 内容?

最直接的想法是为每个角色单独定义 DTO,但这会引入大量重复代码或复杂的类继承结构,维护起来很麻烦。

更好的方式是利用 Jackson 的 JSON 视图(@JsonView) 特性。它能让我们只加个注解就能控制字段是否被序列化,非常方便。

先来看一个简单的数据类:

class Item {
    private int id;
    private String name;
    private String ownerName;

    // getters
}

这个类将作为 REST 接口的响应体返回。

3. 使用 @JsonView 注解

Jackson 提供了 @JsonView 注解来支持多视图的序列化/反序列化。你可以给字段打上标记,告诉 Jackson 在哪种“视图”下才包含该字段。

⚠️ 注意:默认情况下,未标注 @JsonView 的字段也会被包含进 JSON 输出中。如果想严格控制输出范围,可以关闭 DEFAULT_VIEW_INCLUSION 配置。

首先我们定义一个用于区分视图的静态类结构:

class View {
    public static class User {}
    public static class Admin extends User {}
}

然后在字段上添加注解:

@JsonView(View.User.class)
private final int id;
@JsonView(View.User.class)
private final String name;
@JsonView(View.Admin.class)
private final String ownerName;

这样配置后:

  • 当前使用 View.User 进行序列化时,只会输出 idname
  • 使用 View.Admin 时,则三个字段都会输出。

4. 如何与 Spring Security 集成?

接下来就是关键一步:让 Jackson 根据当前用户的权限自动选择合适的视图

为此我们需要:

  1. 定义角色枚举;
  2. 建立角色到视图类的映射关系;
  3. 编写全局控制器增强逻辑,在每次响应前动态设置视图。

4.1 定义角色和映射关系

enum Role {
    ROLE_USER,
    ROLE_ADMIN
}

class View {

    public static final Map<Role, Class> MAPPING = new HashMap<>();

    static {
        MAPPING.put(Role.ADMIN, Admin.class);
        MAPPING.put(Role.USER, User.class);
    }

    //...
}

4.2 全局控制器增强处理

我们可以通过继承 AbstractMappingJacksonResponseBodyAdvice 来拦截所有控制器方法的返回值,并动态设置 @JsonView

@RestControllerAdvice
class SecurityJsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice {

    @Override
    protected void beforeBodyWriteInternal(
      MappingJacksonValue bodyContainer,
      MediaType contentType,
      MethodParameter returnType,
      ServerHttpRequest request,
      ServerHttpResponse response) {
        if (SecurityContextHolder.getContext().getAuthentication() != null
          && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) {
            Collection<? extends GrantedAuthority> authorities
              = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
            List<Class> jsonViews = authorities.stream()
              .map(GrantedAuthority::getAuthority)
              .map(AppConfig.Role::valueOf)
              .map(View.MAPPING::get)
              .collect(Collectors.toList());
            if (jsonViews.size() == 1) {
                bodyContainer.setSerializationView(jsonViews.get(0));
                return;
            }
            throw new IllegalArgumentException("Ambiguous @JsonView declaration for roles "
              + authorities.stream()
              .map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")));
        }
    }
}

✅ 这样一来,所有接口返回的数据都会经过这个增强器处理,自动根据当前用户的角色决定输出哪些字段。

⚠️ 注意:如果有用户同时拥有多个角色,请确保你的逻辑足够清晰,避免出现歧义。

5. 总结

在这篇简短的文章中,我们介绍了如何结合 Spring Security 和 Jackson 的 @JsonView 注解,实现按角色过滤 JSON 输出的功能。

核心思路如下:

  • 利用 @JsonView 控制字段可见性;
  • 结合 Spring Security 获取当前用户角色;
  • 通过全局控制器增强机制动态设置视图;
  • 实现了同一对象根据不同角色返回不同内容的效果,避免了冗余 DTO 类的设计。

这种方案简洁高效,适合大多数需要做权限控制的 REST API 场景。


原始标题:Filtering Jackson JSON Output Based on Spring Security Role | Baeldung