如何在Java关联关系中用@JsonProperty隐藏敏感字段?

来源:NET教程网作者:新加坡程序员头衔:程序员
导读:本期聚焦于新加坡程序员创作的《如何在Java关联关系中用@JsonProperty隐藏敏感字段?》,敬请观看详情。Java实体关联序列化时,密码、身份证号、手机号等敏感字段会不会跟着被输出到接口响应里?这个问题在用户与订单、部门与员工这类关联关系中尤其常见。直接用transient或@JsonIgnore虽然能隐藏字段,但反序列化阶段也会丢掉这些值,导致无法从前端接收或写入数据库。Jackson的@JsonProperty注解提供了access属性,其中WRITE_ONLY模式可以让字段在反序列化时正常读入,在序列化时完全隐藏。本文结合关联实体场景,演示如何用@JsonProperty(access = Access.WRITE_ONLY)精准控制敏感数据流向,同时对比@JsonIgnore、@JsonIgnoreProperties的适用边界,并给出双向关联序列化时容易踩的坑和解决办法。

在Java后端接口开发中,实体之间的关联关系非常常见,例如User与Order、Department与Employee。当使用Jackson把关联对象序列化成JSON返回给前端时,默认会遍历实体中的所有getter方法,把字段值一一输出。如果User实体里含有password、salt、身份证号等敏感字段,这些数据就会随接口响应泄露出去。一个直观的解决办法是给字段加transient关键字,但transient只对Java原生序列化有效,对Jackson无效;另一个办法是使用@JsonIgnore,但它会让字段在反序列化时也无法接收。实际上,Jackson的@JsonProperty注解除了重命名JSON字段,还提供了access属性,可以分别控制字段在序列化和反序列化阶段的可见性,非常适合在关联关系中隐藏敏感数据。

如何在Java关联关系中用@JsonProperty隐藏敏感字段?

一、@JsonProperty的access属性如何工作

@JsonProperty是Jackson中最常用的注解之一,大多数开发者只使用它的value属性来指定JSON字段名,却忽略了access属性。access属性接收一个JsonProperty.Access枚举值,该枚举包含四个选项:AUTO、READ_ONLY、WRITE_ONLY和READ_WRITE。其中READ_ONLY表示字段只在序列化时输出,反序列化时不能从JSON读取;WRITE_ONLY表示字段只在反序列化时读入,序列化时不会输出;READ_WRITE是默认行为,两个阶段都参与;AUTO则根据getter和setter的可见性自动推断。

对于密码这类敏感数据,我们通常希望前端提交注册或登录请求时能够把password传进来,但查询用户信息时接口响应里不能出现password。WRITE_ONLY正好满足这个需求。它不是简单地把字段从JSON中删除,而是在Jackson的数据绑定过程中阻断序列化输出,同时保留反序列化输入。这一点与直接使用@JsonIgnore完全不同,因为@JsonIgnore会同时关闭两个方向。

需要注意的是,access属性不仅对普通字段生效,对关联对象同样生效。如果某个关联属性标注为WRITE_ONLY,那么反序列化时可以从JSON构造关联对象,但序列化时不会把关联对象展开。这种机制在敏感关联关系中非常有用,可以避免把整个子对象暴露出去。

二、在关联实体中隐藏敏感字段的完整示例

下面构建一个常见的用户订单关联场景。User实体包含id、username、password以及一个订单列表,Order实体通过owner字段反向引用User。目标是:用户信息接口返回User时,password不能出现在JSON中;但创建或更新用户时,password可以正常接收。

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import java.util.List;

public class User {
    private Long id;
    private String username;

    @JsonProperty(access = Access.WRITE_ONLY)
    private String password;

    private List<Order> orders;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

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

    public List<Order> getOrders() { return orders; }
    public void setOrders(List<Order> orders) { this.orders = orders; }
}

Order实体同样定义关联关系,并在owner属性上使用@JsonProperty(access = Access.WRITE_ONLY)或者直接使用@JsonIgnore,具体取决于业务是否需要从JSON中接收owner对象。如果创建订单时前端会提交完整的user对象,那么owner字段可以保持可写;如果只提交userId,则owner字段更适合用@JsonIgnore。

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;

public class Order {
    private Long id;
    private String orderNo;

    @JsonProperty(access = Access.WRITE_ONLY)
    private User owner;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getOrderNo() { return orderNo; }
    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }

    public User getOwner() { return owner; }
    public void setOwner(User owner) { this.owner = owner; }
}

当使用ObjectMapper序列化一个User对象时,password字段不会出现在输出中,但orders列表会正常展开。如果Order实体中的owner字段标注为WRITE_ONLY,序列化Order时也不会出现owner,从而避免双向关联导致的无限递归。不过实际项目中,双向关联通常还会搭配@JsonManagedReference和@JsonBackReference使用,以更清晰地管理序列化方向。

反序列化时,即使JSON中没有password字段,只要实体定义了setter,Jackson也能正常处理其他字段;如果JSON中包含password字段,它会被正常赋值。这样就能保证敏感数据只进不出。需要注意的是,WRITE_ONLY只影响Jackson的序列化行为,并不影响数据库持久化。如果使用JPA或MyBatis等框架,字段依然会按照ORM映射规则写入数据库。

三、与@JsonIgnore、@JsonIgnoreProperties的对比

很多开发者在隐藏字段时首先想到@JsonIgnore,但从上面的分析可以看出,@JsonIgnore的语义是“完全忽略”,既不能序列化也不能反序列化。对于密码字段,如果使用@JsonIgnore,前端提交的password不会被接收,登录或注册接口就会丢失密码数据,需要额外通过其他方式处理。@JsonIgnoreProperties通常标注在类级别,可以一次性忽略多个字段,但它的效果与@JsonIgnore相同,同样是双向忽略。

下表对比了三者的适用场景:

注解序列化输出反序列化输入典型场景
@JsonProperty(access = Access.WRITE_ONLY)不输出可接收密码、验证码、支付密钥
@JsonProperty(access = Access.READ_ONLY)输出不可接收数据库生成的主键、创建时间
@JsonIgnore不输出不可接收完全不需要暴露的临时字段
@JsonIgnoreProperties不输出不可接收类级别批量忽略多个字段

在关联关系中,如果子对象只需要通过父对象访问,而不需要从JSON反序列化时直接传入,可以使用@JsonIgnore或@JsonIgnoreProperties来忽略关联属性。但如果业务上需要接收关联对象但不想输出,那么@JsonProperty(access = Access.WRITE_ONLY)就是更合适的选择。例如订单创建接口可能允许前端提交完整的user对象,但订单查询接口不应该把user的详细信息再次展开,这时就可以在Order的owner字段上使用WRITE_ONLY。

另外,如果不想在实体类上添加Jackson注解,也可以使用DTO投影方案。在Service层或Controller层将实体转换为专门的响应DTO,DTO中只包含允许暴露的字段。这种方案与注解相比更灵活,但会增加类的数量和维护成本。对于中小型项目,直接在实体字段上使用@JsonProperty的access属性是成本最低的做法。

四、实战中容易踩的坑

第一个坑是Lombok与@JsonProperty同时使用时的字段名冲突。Lombok会根据字段名自动生成getter和setter,如果@JsonProperty同时指定了value属性,比如@JsonProperty(value = "pwd", access = Access.WRITE_ONLY),反序列化时前端必须传pwd而不是password。很多开发者因为忽略value属性导致字段接收不到,排查时容易误以为是access属性失效。

第二个坑是敏感字段被日志或toString方法输出。即使Jackson序列化时隐藏了password,如果实体类使用Lombok的@Data注解自动生成toString方法,日志打印对象时仍然会把password打出来。建议对包含敏感字段的实体排除toString输出,或者在日志框架中对敏感字段做脱敏处理。同样,调试时直接打印对象也可能泄露数据。

第三个坑是关联集合懒加载导致的序列化异常。在JPA中,List<Order> orders默认是懒加载的,如果Service层事务已经关闭,Jackson序列化User时访问orders会抛出LazyInitializationException。解决方式通常是在查询时使用JOIN FETCH,或者将关联集合设置为EAGER,或者在DTO投影中处理。这与隐藏敏感数据无关,但在关联关系序列化时经常同时出现。

第四个坑是WRITE_ONLY字段在更新操作中的行为。对于更新场景,如果前端提交的JSON中缺少password字段,Jackson不会修改实体中已有的password值;但如果JSON中显式传了null,password会被设置为null。这可能导致数据库中的密码被意外清空。实际开发中建议在更新用户时先根据id查询已有实体,再手动控制敏感字段的赋值逻辑,或者使用专门的更新DTO来避免这类问题。

第五个坑是Map类型字段的序列化行为。如果实体中有一个Map<String, Object>类型的扩展字段,里面可能包含敏感key,Jackson不会自动对Map内部的值应用@JsonProperty注解。这种情况下需要自定义序列化器,或者在写入Map之前就移除敏感数据。关联关系中的集合字段如果包含敏感对象,同样需要逐个检查对象内部的注解是否生效。

总结来说,在Java关联关系中隐藏敏感数据,@JsonProperty(access = Access.WRITE_ONLY)是一个精准且低成本的方案。它比@JsonIgnore更灵活,既保证了反序列化阶段的数据完整,又切断了序列化阶段的泄露路径。使用时需要注意关联方向、Lombok协作、懒加载以及更新操作中的空值处理,才能真正把敏感数据控制在安全边界内。

Java关联关系隐藏敏感数据@JsonProperty修改时间:2026-10-03 22:41:20

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1003/65277.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。