1. 概述
在本篇教程中,我们将展示如何根据 Spring Security 中定义的用户角色,动态地过滤 JSON 序列化的输出内容。
2. 为什么需要过滤?
考虑一个典型的使用场景:我们的 Web 应用面向不同角色的用户,比如 User 和 Admin。
需求很简单也很常见:
- ✅ 管理员(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
进行序列化时,只会输出id
和name
; - 使用
View.Admin
时,则三个字段都会输出。
4. 如何与 Spring Security 集成?
接下来就是关键一步:让 Jackson 根据当前用户的权限自动选择合适的视图。
为此我们需要:
- 定义角色枚举;
- 建立角色到视图类的映射关系;
- 编写全局控制器增强逻辑,在每次响应前动态设置视图。
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 场景。