Spring Boot 中字段序列化与反序列化的精准控制:从 @JsonIgnore 到 @JsonProperty

在 Spring Boot 开发中,处理 JSON 数据时经常遇到一个痛点:如何精确控制字段的“输入”与“输出”。很多时候,我们希望某个字段在返回给前端时被隐藏(如密码、内部ID),但在接收前端提交的数据时又需要能够被赋值。简单地使用 @JsonIgnore 往往会导致“一刀切”,既忽略了返回,也阻断了接收。本文将深入探讨这一问题的根源,并展示如何使用 @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 实现更精细的控制。

为什么 @JsonIgnore 不够用?

Jackson 是 Spring Boot 默认的 JSON 处理库。@JsonIgnore 注解的作用非常直观:它告诉 Jackson 在序列化(对象转 JSON)和反序列化(JSON 转对象)过程中都忽略该字段。

public class User {
    private String username;
    
    @JsonIgnore
    private String password;
}

在上述代码中,当我们将 User 对象转换为 JSON 返回给客户端时,password 字段确实不会出现在 JSON 字符串中。然而,这也带来了一个副作用:如果客户端在注册或修改密码时提交了 password 字段,Spring MVC 在将 JSON 绑定到 User 对象时,也会直接忽略这个字段。这意味着后端永远无法通过标准的数据绑定机制接收到这个值。

这在以下场景中是不可接受的:

  1. 用户注册/登录:前端需要发送密码,但后端不应在响应中回显密码。
  2. 敏感信息更新:如重置密码、修改安全邮箱等,需要接收新值但不希望旧值或新值在查询接口中泄露。

解决方案:@JsonProperty(access = …)

为了解决上述问题,Jackson 提供了 @JsonProperty 注解的 access 属性。通过设置不同的访问模式,我们可以独立控制字段的读取(序列化)和写入(反序列化)行为。

核心属性说明

枚举值 含义 适用场景
READ_WRITE 默认值,既可读也可写 普通业务字段
READ_ONLY 仅可读(序列化),不可写(反序列化) 计算字段、内部状态标识
WRITE_ONLY 仅可写(反序列化),不可读(序列化) 密码、秘密令牌、一次性验证码

实战示例:只接收不返回

针对前文提到的密码场景,正确的做法是使用 WRITE_ONLY

import com.fasterxml.jackson.annotation.JsonProperty;

public class UserDTO {
    private String username;
    /**
     * WRITE_ONLY 表示:
     * 1. 反序列化时:JSON 中的 "password" 会被映射到此字段。
     * 2. 序列化时:此字段不会被包含在生成的 JSON 中。
     */
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;
    // getters and setters
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}
行为验证
  1. 接收数据(POST /register)
    • 请求体:{"username": "john", "password": "secret123"}
    • 结果:UserDTO 对象的 password 字段成功被赋值为 "secret123"
  2. 返回数据(GET /user/profile)
    • 响应体:{"username": "john"}
    • 结果:password 字段完全不出现在 JSON 中,即使对象内部持有该值。

进阶建议:结合其他注解增强安全性

虽然 WRITE_ONLY 解决了基本的传输层隔离,但在实际生产中,还需注意以下几点:

1. 日志脱敏

即使 JSON 中不包含密码,如果开发者不小心将对象打印到日志中(如 log.info("Received user: {}", user)),toString() 方法可能会泄露敏感信息。建议重写 toString() 或使用专门的日志脱敏工具。

2. 数据库持久化隔离

确保在 ORM 框架(如 JPA/Hibernate)中也做了相应配置。例如,在使用 @Entity 时,如果 password 字段同时用于数据库存储,需确保其加密逻辑正确,且不要在查询所有用户列表时意外加载明文密码。

3. 区分 READ_ONLY 场景

对于某些字段,如 createdAt(创建时间),我们通常希望它在创建后由后端生成,前端不应也无法修改。此时应使用 READ_ONLY

@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private LocalDateTime createdAt;

这样,前端提交的 createdAt 将被忽略,而后端返回时会包含该字段。

总结

在 Spring Boot 中处理 JSON 字段可见性时,不要盲目使用 @JsonIgnore@JsonIgnore 是双向屏蔽,而 @JsonProperty(access = ...) 提供了单向控制的灵活性。

  • 如果需要完全隐藏字段(既不接收也不返回),使用 @JsonIgnore
  • 如果需要接收但不返回(如密码),使用 @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  • 如果需要返回但不接收(如系统生成 ID),使用 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    掌握这些细微差别,不仅能提升 API 的安全性,还能避免许多因数据绑定失败导致的隐蔽 Bug。- - - 3. - 2. * * * 3.

更多推荐