表单提交明明有值,后台对象却一直为空,原因不一定在前端。2018 年项目里,公共层根据自定义 @BindField 收集允许绑定的字段;未标注字段被 WebDataBinder 的允许列表拦住了。

Spring 默认行为之外的项目规则

当时公共代码大意如下:

if (!allowedList.isEmpty()) {
    binder.setAllowedFields(allowedList.toArray(new String[0]));
} else {
    binder.setAllowedFields("");
}

@BindField 不是 Spring 所有表单必需的标准注解,而是这个项目扫描允许字段的约定。关键在最后的 setAllowedFields(""):它传入一个含空字符串的数组,不是没有限制的空数组或 null。

显式设置允许字段

下面展示一个独立的表单接收结构,不依赖原项目的扫描器:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AllowedFieldsController {
    public static class ProfileForm {
        private String displayName;
        private String role;
        public String getDisplayName() { return displayName; }
        public void setDisplayName(String value) { displayName = value; }
        public String getRole() { return role; }
        public void setRole(String value) { role = value; }
    }

    @InitBinder("profile")
    public void profileBinder(WebDataBinder binder) {
        binder.setAllowedFields("displayName");
    }

    @PostMapping("/examples/profile-binding")
    public Map<String, Object> inspect(
            @ModelAttribute("profile") ProfileForm form,
            BindingResult result) {
        Map<String, Object> output = new LinkedHashMap<>();
        output.put("displayName", form.getDisplayName());
        output.put("role", form.getRole());
        output.put("suppressedFields",
                Arrays.asList(result.getSuppressedFields()));
        return output;
    }
}

示例只演示绑定,没有写数据库。提交 displayName 与 role 时,只有前者允许绑定,后者可以从 suppressedFields 观察;不应把拒绝绑定字段的原值写进日志。实际接口通常不向客户端暴露内部字段诊断信息。

三种“空”的含义不同

Spring 5 的 DataBinder 没有限制时默认允许所有字段;setAllowedFields 的 null 或空数组不等于“全部拒绝”。而包含空字符串的列表一般不匹配正常字段名,才产生旧项目的效果。Spring 5 DataBinder API

支持的通配模式也会扩大范围,尤其要检查嵌套属性。不要把敏感字段 role、ownerId 等留在可任意提交的对象里,再只依赖前端隐藏输入控件。

收到参数不等于可以接受它

白名单可能正在防止越权赋值,不能为了“所有字段都有值”就删掉。应确认允许字段是否符合业务,然后检查属性访问器、类型转换、校验和授权。

这讨论的是模型或表单数据绑定。@RequestBody JSON 通常由消息转换器反序列化,不会因为设了同一份 allowedFields 就自动得到等价保护,应优先使用专门的输入 DTO,并在业务边界验证。