Spring 启动请求正文 (POJO) 不处理特殊字符

Spring Boot Request Body (POJO) doesn't handle special characters

我有一个 Spring 带 REST 的启动 API:

    @Operation(summary = "Update your user")
    @PatchMapping(consumes = {MediaType.APPLICATION_JSON_VALUE})
    @PreAuthorize("hasAuthority('MANAGE_MY_ACCOUNT')")
    @ApiResponse(responseCode = "204", description = "Successfully updated user")
    @ApiResponse(responseCode = "400", description = "Cannot update user",
            content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
    @ApiResponse(responseCode = "404", description = "Unknown User Id",
            content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
    ResponseEntity<Void> updateUser(@RequestBody UpdateUserRequest request);

它在请求正文中接受 UpdateUserRequest 作为 POJO。

@Getter
@NoArgsConstructor
public class UpdateUserRequest {
    @Length(max = 100)
    private String name;
    @Length(max = 100)
    private String givenName;
    @Length(max = 100)
    private String familyName;
}

当我使用以下请求调用 API 时:

{
    "name": "Rafał Laskowski",
    "givenName": "Rafał",
    "familyName": "Laskowski"
}

namegivenName 编码不正确。我得到的是 RafaÅ。在调试 @RestController 时,我注意到这些名称被错误地转换为字节数组,如下所示:

目前我尝试过的:

我认为它可能是 Jackson 的对象映射器配置,但我读到它不是这种情况,因为 JSON 只接受 UTF-x 编码。 我不知道为什么没有从请求中正确读取字符 ł

看起来问题出在我们应用程序的安全范围内。我们已经通过以下 class:

实现了 XSS 安全
public class XSSRequestWrapper extends HttpServletRequestWrapper {

    private final HttpServletRequest request;
    private final ResettableServletInputStream servletStream;
    private byte[] rawData;

    public XSSRequestWrapper(HttpServletRequest request) {
        super(request);
        this.request = request;
        this.servletStream = new ResettableServletInputStream();
    }

    public static String stripXSS(String value) {
        if (value == null) {
            return null;
        }
        value = ESAPI.encoder()
                .canonicalize(value)
                .replaceAll("[=10=]", "");
        return Jsoup.clean(value, Whitelist.none());
    }

    public void resetInputStream(byte[] newRawData) {
        rawData = newRawData;
        servletStream.stream = new ByteArrayInputStream(newRawData);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.UTF_8);
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.UTF_8);
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return new BufferedReader(new InputStreamReader(servletStream));
    }

    @Override
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = stripXSS(values[i]);
        }
        return encodedValues;
    }

    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        return stripXSS(value);
    }

    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        return stripXSS(value);
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> result = new ArrayList<>();
        Enumeration<String> headers = super.getHeaders(name);
        while (headers.hasMoreElements()) {
            String header = headers.nextElement();
            String[] tokens = header.split(",");
            for (String token : tokens) {
                result.add(stripXSS(token));
            }
        }
        return Collections.enumeration(result);
    }

    private class ResettableServletInputStream extends ServletInputStream {

        private InputStream stream;

        @Override
        public int read() throws IOException {
            return stream.read();
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }
    }
}

解决问题的真正方法是改变 getInputStream() 方法的工作方式。

已更改:

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.UTF_8);
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(this.request.getReader(), Charsets.ISO_8859_1);
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

问题解决了。 免责声明:我不知道为什么:)