You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3 RestClient发送对象请求失败,字符串请求正常

Spring Boot 3 RestClient POST请求分块编码导致403问题排查与解决

问题现象

使用Spring Boot 3的Http RestClient发送POST请求时,字符串格式的请求体可正常返回200,但发送AddAppRole对象时返回403 Forbidden。对比HTTP日志可见:

  • 字符串请求:通过Content-Length指定内容长度,无分块编码
  • 对象请求:采用Transfer-Encoding: chunked编码,请求体中出现7f、0这类分块标识,触发服务端异常拦截

原因分析

RestClient处理对象请求体时,默认以流式方式序列化对象,无法提前计算请求体总长度,因此自动启用分块编码。而目标服务端可能对分块编码的请求做了拦截校验,或是无法正确解析分块格式的请求体,最终返回403。

解决方法

1. 手动序列化对象为字符串后发送

借助Jackson将AddAppRole对象序列化为JSON字符串,再传入body()方法,让RestClient自动计算Content-Length,避免分块编码:

// 注入Spring管理的ObjectMapper
@Autowired
private ObjectMapper objectMapper;

// 序列化对象为JSON字符串
String roleJson = objectMapper.writeValueAsString(addAppRole);

ResponseEntity<ApiResponse> result = restClient
    .post()
    .uri("dev/v1/app-roles")
    .accept(APPLICATION_JSON)
    .contentType(APPLICATION_JSON)
    .body(roleJson)
    .retrieve()
    .toEntity(ApiResponse.class);

2. 全局配置RestClient禁用分块编码

通过自定义底层HttpClient的请求配置,全局禁用分块编码。注意此方式会影响所有请求,大请求体场景可能导致内存占用过高:

@Bean
public RestClient restClient() {
    CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
        .setDefaultRequestConfig(RequestConfig.custom()
            .setChunkedEnabled(false) // 禁用分块编码
            .build())
        .build();

    ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

    return RestClient.builder()
        .requestFactory(requestFactory)
        .build();
}

3. 提前计算请求体长度并手动设置

先将对象序列化为字节数组,计算长度后手动设置Content-Length请求头:

byte[] requestBodyBytes = objectMapper.writeValueAsBytes(addAppRole);

ResponseEntity<ApiResponse> result = restClient
    .post()
    .uri("dev/v1/app-roles")
    .accept(APPLICATION_JSON)
    .contentType(APPLICATION_JSON)
    .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(requestBodyBytes.length))
    .body(requestBodyBytes)
    .retrieve()
    .toEntity(ApiResponse.class);

原问题相关代码与日志

字符串请求成功代码

private final String roleBody = """
    {"ApplicationID":"XXXX","RolesValues":["ROLE"],"retainPreviousRoles":"true"}
    """;

ResponseEntity<ApiResponse> result = restClient
    .post()
    .uri("dev/v1/app-roles")
    .accept(APPLICATION_JSON)
    .contentType(APPLICATION_JSON)
    .body(roleBody)
    .retrieve()
    .toEntity(ApiResponse.class);

字符串请求日志

User-Agent: Apache-HttpClient/5.2.3 (Java/17.0.11)
"POST /dev/v1/app-roles HTTP/1.1[\r][
]"
"Content-Type: application/json[\r][
]"
"Accept: application/json[\r][
]"
"Accept-Encoding: gzip, x-gzip, deflate[\r][
]"
"Content-Length: 128[\r][
]"
"Host: appregapi.bpglobal.com[\r][
]"
"Connection: keep-alive[\r][
]"
"User-Agent: Apache-HttpClient/5.2.3 (Java/17.0.11)[\r][
]"
"[\r][
]"
"{\"ApplicationID\":\"XXXX\",\"RolesValues\":[\"ROLE\"],\"retainPreviousRoles\":\"true\"}[
]"
"HTTP/1.1 200 OK[\r][
]"
"Transfer-Encoding: chunked[\r][
]"
"Content-Type: application/json; charset=utf-8[\r][
]"
"Content-Encoding: gzip[\r][
]"
"Vary: Accept-Encoding[\r][
]"
"Date: Sat, 08 Jun 2024 20:22:09 GMT[\r][
]"
"[\r][
]"
"44[\r][
]"

对象请求失败代码

AddAppRole addAppRole = AddAppRole.builder()
    .applicationId("XXXX")
    .rolesValues(List.of("ROLE"))
    .retainPreviousRoles("true")
    .build();

ResponseEntity<ApiResponse> result = restClient
    .post()
    .uri("dev/v1/app-roles")
    .accept(APPLICATION_JSON)
    .contentType(APPLICATION_JSON)
    .body(addAppRole)
    .retrieve()
    .toEntity(ApiResponse.class);

对象请求日志

User-Agent: Apache-HttpClient/5.2.3 (Java/17.0.11)
"POST /dev/v1/app-roles HTTP/1.1[\r][
]"
"Content-Type: application/json[\r][
]"
"Accept: application/json[\r][
]"
"Accept-Encoding: gzip, x-gzip, deflate[\r][
]"
"Transfer-Encoding: chunked[\r][
]"
"Host: appregapi.bpglobal.com[\r][
]"
"Connection: keep-alive[\r][
]"
"User-Agent: Apache-HttpClient/5.2.3 (Java/17.0.11)[\r][
]"
"[\r][
]"
"7f[\r][
]"
"{\"ApplicationID\":\"XXXX\",\"RolesValues\":[\"ROLE\"],\"retainPreviousRoles\":\"true\"}[\r][
]"
"0[\r][
]"
"[\r][
]"
"HTTP/1.1 403 Forbidden[\r][
]"
"Transfer-Encoding: chunked[\r][
]"
"Content-Type: application/json; charset=utf-8[\r][
]"
"Date: Sat, 08 Jun 2024 20:23:35 GMT[\r][
]"
"[\r][
]"
"46[\r][
]"
"{[\r][
]"
HTTP/1.1 403 Forbidden

AddAppRole类定义

@Builder
@JsonPropertyOrder(value = {"ApplicationID", "RolesValues", "retainPreviousRoles"})
public class AddAppRole {
    @JsonProperty("ApplicationID")
    private final String applicationId;

    @JsonProperty("RolesValues")
    private final List<String> rolesValues;

    private final String retainPreviousRoles;
}

内容的提问来源于stack exchange,提问作者user25486013

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:34:53