目录确认需求定义枚举和对象实现转换逻辑方案一:精准攻击方案二:全范围攻击测试总结确认需求 需求与前文类似,只不过这里需要是在 RequestBody 中使用。与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 JS
需求与前文类似,只不过这里需要是在 RequestBody 中使用。与前文不同的是,这种请求是通过 Http Body 的方式传输到后端,通常是 JSON 或 xml 格式,spring 默认借助 Jackson 反序列化为对象。
同样的,我们需要在枚举中定义 int 类型的 id、String 类型的 code,id 取值不限于序号(即从 0 开始的 orinal 数据),code 不限于 name。客户端请求过程中,可以传 id,可以传 code,也可以传 name。服务端只需要在对象中定义一个枚举参数,不需要额外的转换,即可得到枚举值。
好了,接下来我们定义一下枚举对象。
先定义我们的枚举类GenderIdCodeEnum,包含 id 和 code 两个属性:
public enum GenderIdCodeEnum implements IdCodeBaseEnum {
MALE(1, "male"),
FEMALE(2, "female");
private final Integer id;
private final String code;
GenderIdCodeEnum(Integer id, String code) {
this.id = id;
this.code = code;
}
@Override
public String getCode() {
return code;
}
@Override
public Integer getId() {
return id;
}
}
这个枚举类的要求与前文一致,不清楚的可以再去看一下。
在定义一个包装类GenderIdCodeRequestBody
,用于接收 json 数据的请求体:
@Data
public class GenderIdCodeRequestBody {
private String name;
private GenderIdCodeEnum gender;
private long timestamp;
}
除了GenderIdCodeEnum
参数外,其他都是示例,所以随便定义一下。
前奏铺垫好,接下来入正题了。Jackson 提供了两种方案:
这种方案中,我们首先需要实现JsonDeserialize
抽象类:
public class IdCodeToEnumDeserializer extends JsonDeserializer<BaseEnum> {
@Override
public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
final String param = jsonParser.getText();// 1
final JsonStreamContext parsinGContext = jsonParser.getParsingContext();// 2
final String currentName = parsingContext.getCurrentName();// 3
final Object currentValue = parsingContext.getCurrentValue();// 4
try {
final Field declaredField = currentValue.getClass().getDeclaredField(currentName);// 5
final Class<?> targetType = declaredField.getType();// 6
final Method createMethod = targetType.getDeclaredMethod("create", Object.class);// 7
return (BaseEnum) createMethod.invoke(null, param);// 8
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | NoSuchFieldException e) {
throw new CodeBaseException(ErrorResponseEnum.PARAMS_ENUM_NOT_MATCH, new Object[] {param}, "", e);
}
}
}
然后在指定枚举字段上定义@JsonDeserialize
注解,比如:
@JsonDeserialize(using = IdCodeToEnumDeserializer.class)
private GenderIdCodeEnum gender;
具体说一下每行的作用:
我们来看一下枚举类中定义的create
方法:
public static GenderIdCodeEnum create(Object code) {
final String stringCode = code.toString();
final Integer intCode = BaseEnum.adapter(stringCode);
for (GenderIdCodeEnum item : values()) {
if (Objects.equals(stringCode, item.name())) {
return item;
}
if (Objects.equals(item.getCode(), stringCode)) {
return item;
}
if (Objects.equals(item.getId(), intCode)) {
return item;
}
}
return null;
}
为了性能考虑,我们可以提前定义三组 map,分别以 id、code、name 为 key,以枚举值为 value,这样就可以通过 O(1) 的时间复杂度返回了。可以参考前文的Converter类的实现逻辑。
这样,我们就可以实现精准转换了。
这种方案是全范围攻击了,只要是 Jackson 参与的反序列化,只要其中有目标枚举参数,就会受到这种进入这种方案的逻辑中。这种方案是在枚举类中定义一个静态转换方法,通过@JsonCreator注解注释,Jackson 就会自动转换了。
这个方法的定义与方案一中的create方法完全一致,所以只需要在create方法上加上注解即可:
@JsonCreator(mode = Mode.DELEGATING)
public static GenderIdCodeEnum create(Object code) {
final String stringCode = code.toString();
final Integer intCode = BaseEnum.adapter(stringCode);
for (GenderIdCodeEnum item : values()) {
if (Objects.equals(stringCode, item.name())) {
return item;
}
if (Objects.equals(item.getCode(), stringCode)) {
return item;
}
if (Objects.equals(item.getId(), intCode)) {
return item;
}
}
return null;
}
其中Mode类有四个值:DEFAULT、DELEGATING、PROPERTIES、DISABLED,这四种的差别会在原理篇中说明。还是那句话,对于应用类技术,我们可以先知其然,再知其所以然,也一定要知其所以然。
先定义一个 controller 方法:
@PostMapping("gender-id-code-request-body")
public GenderIdCodeRequestBody bodyGenderIdCode(@RequestBody GenderIdCodeRequestBody genderRequest) {
genderRequest.setTimestamp(System.currentTimeMillis());
return genderRequest;
}
然后定义测试用例,还是借助 JUnit5:
@ParameterizedTest
@ValueSource(strings = {"\"MALE\"", "\"male\"", "\"1\"", "1"})
void postGenderIdCode(String gender) throws Exception {
final String result = mockmvc.perfORM(
MockMvcRequestBuilders.post("/echo/gender-id-code-request-body")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content("{\"gender\": " + gender + ", \"name\": \"看山\"}")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse()
.getContentAsString();
ObjectMapper objectMapper = new ObjectMapper();
final GenderIdCodeRequestBody genderRequest = objectMapper.readValue(result, GenderIdCodeRequestBody.class);
Assertions.assertEquals(GenderIdCodeEnum.MALE, genderRequest.getGender());
Assertions.assertEquals("看山", genderRequest.getName());
Assertions.assertTrue(genderRequest.getTimestamp() > 0);
}
本文主要说明了如何在 RequestBody 中优雅的使用枚举参数,借助了 Jackson 的反序列化扩展,可以定制类型转换逻辑。
本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注编程界的更多内容!
--结束END--
本文标题: 一篇文章带了解如何用SpringBoot在RequestBody中优雅的使用枚举参数
本文链接: https://www.lsjlt.com/news/98.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2023-09-30
2023-09-30
2023-09-30
2023-09-30
2023-09-30
2023-09-30
2023-09-30
2023-09-29
2023-09-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0