批量赋值安全问题:如何在Jersey框架中使用Spring的@InitBinder?
首先得给你提个醒:你在代码里用的Spring MVC的@InitBinder注解,在Jersey框架里是不会生效的——这俩框架的请求数据绑定机制完全独立,Jersey根本不识别这个Spring专属的注解。不过别担心,我们有好几种方法可以实现你想要的「只允许设置指定属性,其余设为null」的需求,下面逐个给你讲:
方法1:用Jackson注解实现字段白名单(最简单)
因为Jersey默认用Jackson处理JSON请求/响应,所以直接在你的AccountRequest实体类上添加Jackson注解就能轻松实现过滤:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; // 严格模式:只允许下面标注@JsonProperty的字段被赋值,其他字段直接忽略 @JsonIgnoreProperties(value = {"*"}, allowSetters = false) public class AccountRequest { // 只允许这个字段被设置 @JsonProperty("allowedField") private String allowedField; // 其他字段不标注@JsonProperty,反序列化时不会被赋值(保持null) private String disallowedField1; private Integer disallowedField2; // getter和setter方法... }
如果只是想忽略未知字段(比如请求里多传的字段不处理),也可以用更宽松的配置:
@JsonIgnoreProperties(ignoreUnknown = true) public class AccountRequest { // ... }
这种方式下,请求里存在但实体类没有的字段会被跳过,实体类已有的字段还是会被正常赋值——如果你需要严格的白名单,还是用第一种注解配置更安全。
方法2:自定义Jersey MessageBodyReader(灵活可控)
如果需要针对不同接口设置不同的字段白名单(比如同一个实体类在不同接口里允许的字段不一样),可以自定义Jersey的MessageBodyReader,手动控制请求体的解析过程:
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.Provider; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; @Provider @Consumes(MediaType.APPLICATION_JSON) public class AccountRequestReader implements MessageBodyReader<AccountRequest> { private final ObjectMapper objectMapper = new ObjectMapper(); // 指定这个Reader只处理AccountRequest类型的参数 @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == AccountRequest.class; } @Override public AccountRequest readFrom(Class<AccountRequest> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { JsonNode requestNode = objectMapper.readTree(entityStream); AccountRequest filteredRequest = new AccountRequest(); // 只设置你允许的字段,其他字段保持null if (requestNode.has("allowedField")) { filteredRequest.setAllowedField(requestNode.get("allowedField").asText()); } // 可以根据接口的注解(比如自定义注解)动态调整允许的字段,这里简化处理 return filteredRequest; } }
写完这个类后,记得把它注册到Jersey的配置类里(比如继承ResourceConfig的类):
public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(AccountRequestReader.class); register(RequestService.class); // 其他注册项... } }
方法3:Spring集成场景下用RequestBodyAdvice
如果你的项目是Spring Boot集成Jersey的(比如用spring-boot-starter-jersey依赖),可以借助Spring的RequestBodyAdvice来实现全局的请求体过滤:
import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; import java.lang.reflect.Type; @ControllerAdvice public class RequestPropertyFilterAdvice extends RequestBodyAdviceAdapter { // 指定只处理AccountRequest类型的请求体 @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return targetType.getTypeName().equals(AccountRequest.class.getName()); } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { AccountRequest originalRequest = (AccountRequest) body; // 创建新实例,只复制允许的字段,确保其他字段为null AccountRequest filteredRequest = new AccountRequest(); filteredRequest.setAllowedField(originalRequest.getAllowedField()); // 其他不允许的字段不复制,保持默认null return filteredRequest; } }
这种方式适合需要全局统一过滤的场景,Spring会自动拦截所有符合条件的请求体,帮你完成过滤。
最后给你修正一下原来的代码里的问题:把无效的@InitBinder方法去掉,换成上面的任意一种方案就行。比如用Jackson注解的话,你的RequestService可以保持不变,只需要修改AccountRequest类。
内容的提问来源于stack exchange,提问作者Md. Ruzdi Islam

