





















这是解决 Mass Assignment 最优雅、最彻底的方式。
核心思想:永远不要把数据库实体类(Entity)直接暴露给 Controller 接收参数。针对不同的接口,创建专门的数据传输对象(DTO),DTO 中只包含允许前端修改的字段。
❌ 危险代码(直接用 Entity):
@Data
@Table(name = "t_user")
public class User {
private String username;
private String password;
private Boolean isAdmin; // 敏感字段!
}
@PostMapping("/register")
public Result register(@RequestBody User user) { // 危险!前端传 isAdmin=true 就进去了
userService.save(user);
return Result.success();
}
✅ 安全代码(使用 DTO):
// 1. 专门用于注册的 DTO,没有 isAdmin 字段
@Data
public class UserRegisterDTO {
private String username;
private String password;
// 就算黑客传了 isAdmin,因为 DTO 里没这个属性,Spring 根本不会处理
}
// 2. Controller 使用 DTO 接收
@PostMapping("/register")
public Result register(@RequestBody UserRegisterDTO dto) {
// 3. 将 DTO 转换为 Entity,敏感字段在后端硬编码赋值
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(dto.getPassword());
user.setIsAdmin(false); // 只能由后端逻辑控制
userService.save(user);
return Result.success();
}
如果你在某些场景下必须复用包含了敏感字段的类,可以通过 Jackson 注解来控制反序列化(前端 -> 后端)的行为。
核心思想:在敏感字段上加注解,告诉 Spring 在把 JSON 转成对象时,忽略前端传来的该字段。
✅ 使用 @JsonIgnore(双向屏蔽):
@Data
public class UserDTO {
private String username;
@JsonIgnore // 无论是前端传给后端(反序列化),还是后端传给前端(序列化),都会忽略该字段
private Boolean isAdmin;
}
✅ 使用 @JsonProperty(access = Access.READ_ONLY)(单向屏蔽,更推荐):
@Data
public class UserDTO {
private String username;
// READ_ONLY:后端可以传给前端展示(序列化),但拒绝前端传给后端修改(反序列化)
@JsonProperty(access = Access.READ_ONLY)
private Boolean isAdmin;
}
如果你在维护老旧的 MVC 项目,必须用无注解的实体类接收 x-www-form-urlencoded 表单数据,可以通过 @InitBinder 设置允许绑定的字段白名单。
核心思想:明确告诉 Spring,这个请求只允许绑定这几个字段,其他的统统忽略。
✅ 安全代码:
@PostMapping("/register")
public Result register(User user) {
userService.save(user);
return Result.success();
}
// 在 Controller 中添加此方法,仅对当前 Controller 生效
@InitBinder("user") // "user" 是参数的变量名
public void initBinder(WebDataBinder binder) {
// 白名单机制:只允许绑定这两个字段,isAdmin 会被自动忽略
binder.setAllowedFields("username", "password");
// 或者用黑名单(不推荐,容易漏掉新增的敏感字段):
// binder.setDisallowedFields("isAdmin");
}
Mass Assignment 漏洞之所以能生效,底层是因为 Spring 依赖了 setXxx() 方法。如果你不让实体类提供 Setter,漏洞自然无法利用。
核心思想:实体类只提供包含必要参数的构造函数,状态变更只能通过具有业务含义的方法触发。
✅ 安全代码:
// 不使用 @Data,不提供无参构造和 setter
public class User {
private Long id;
private String username;
private String password;
private Boolean isAdmin;
// 只提供全参构造,或必要的业务构造
public User(String username, String password) {
this.username = username;
this.password = password;
this.isAdmin = false; // 默认值在构造时确定
}
// 只提供 Getter,不提供 Setter
// 如果需要修改状态,提供具有业务含义的方法
public void promoteToAdmin() {
// 这里可以加入权限校验逻辑
this.isAdmin = true;
}
}
| 防御策略 | 适用场景 | 推荐指数 |
|---|---|---|
| DTO 隔离 | 所有前后端分离项目,所有 RESTful API | ⭐⭐⭐⭐⭐ |
| Jackson 注解 | 复用类时,需精细控制某个字段的读写权限 | ⭐⭐⭐⭐ |
| DataBinder 白名单 | 传统表单提交,无注解对象绑定 | ⭐⭐⭐ |
| 移除 Setter | DDD 领域驱动设计,核心业务逻辑层 | ⭐⭐⭐⭐ (架构层面) |
| 终极口诀:入参用 DTO,出参用 VO/Entity,坚决不用 Entity 直接接参! 只要遵守这条规则,Mass Assignment 漏洞就永远找不到你。 |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。