You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Rest API处理XML请求特殊字符报错问题及解决方案

这个问题我之前对接第三方XML接口时也碰到过,本质是XML语法的硬性规则在起作用,我给你拆解下原因和对应的解决办法:

为什么会报错?

XML语法里,&预定义的特殊字符,它的作用是标记实体引用(比如&amp;才是合法的&符号表示,还有&lt;代表<、&gt;代表>这些)。当XML解析器看到单独的&,但后面没有跟合法的实体名称时,就会直接抛出SAXParseException——这不是Java或者Spring的bug,是XML规范要求必须这么做。

你的请求负载里DESCRIPTION="Johnson & Johnsons"直接用了未转义的&,完全违反了XML语法,所以解析器直接拒绝处理了。

怎么解决?

分两种场景来处理,优先选最规范的那种:

场景1:能要求请求发送方修改

最稳妥的做法是让请求方按照XML规范转义特殊字符,把&替换成&amp;就行,其他特殊字符如果出现也要对应转义。修正后的请求应该是这样:

<NAMES> <NAME ID="P2" DESCRIPTION="Johnson &amp; Johnsons" /> </NAMES>

这样Spring默认的JAXB解析器就能正常解析,不需要改任何代码。

场景2:没法改请求方,必须在服务端兼容

如果请求方是第三方系统没法修改,那只能自定义XML解析逻辑,让它容忍未转义的&(注意:这是违反XML规范的操作,可能带来安全风险,一定要谨慎)。

给你两种实现方式:

方式1:用Jackson XML替代JAXB

Spring默认用JAXB解析XML,我们可以换成Jackson的XML解析器,它支持宽松配置:

  1. 先添加Jackson XML依赖(如果还没加):
<!-- Maven -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
  1. 配置自定义的XML消息转换器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;

import java.util.List;

@Configuration
public class XmlWebConfig implements WebMvcConfigurer {

    @Bean
    public MappingJackson2XmlHttpMessageConverter customXmlConverter() {
        XmlMapper xmlMapper = new XmlMapper();
        // 允许未转义的特殊字符
        xmlMapper.configure(ToXmlGenerator.Feature.ALLOW_UNQUOTED_CONTENT_CHARS, true);
        return new MappingJackson2XmlHttpMessageConverter(xmlMapper);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 移除默认的JAXB转换器,用自定义的Jackson XML转换器替代
        converters.removeIf(converter -> converter instanceof org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter);
        converters.add(customXmlConverter());
    }
}

这样配置后,Spring就会用Jackson的XML解析器来处理请求,就能容忍未转义的&了。

方式2:修改JAXB的SAX解析器配置

如果你想继续用JAXB,可以给它配置一个宽松的SAX解析器:

// 可以封装成一个工具类,或者在Controller里手动解析(不推荐在Controller里写,最好封装成Converter)
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public class XmlUnmarshallerUtil {
    public static <T> T unmarshal(Class<T> clazz, InputStream inputStream) throws Exception {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        // 关闭DTD验证和命名空间,开启宽松模式
        spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        spf.setFeature("http://xml.org/sax/features/validation", false);
        // 关键配置:允许未转义的&
        spf.setFeature("http://apache.org/xml/features/allow-unquoted-attributes", true);
        SAXParser saxParser = spf.newSAXParser();

        JAXBContext jc = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setProperty("org.xml.sax.XMLReader", saxParser.getXMLReader());
        return (T) unmarshaller.unmarshal(inputStream);
    }
}

然后在Controller里手动调用这个工具类解析请求体,而不是用@RequestBody

@PostMapping(value = "/UpdateSomeThings", consumes = { MediaType.APPLICATION_XML })
public ResponseEntity<SomeResponseModel> UpdateSomeThings(
        @PathVariable("mId") String mId,
        HttpServletRequest request) throws Exception {
    SomeMapModel reqBody = XmlUnmarshallerUtil.unmarshal(SomeMapModel.class, request.getInputStream());
    // 后续逻辑...
    return new ResponseEntity<>(response, HttpStatus.OK);
}

重要提醒

  • 优先选择场景1的规范方案,违反XML规范的宽松解析可能带来XXE攻击风险,或者解析其他合法XML时出现奇怪的问题。
  • 如果必须用宽松解析,一定要对输入做严格的校验和过滤,避免恶意XML注入。

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

火山引擎 最新活动