解析xml时如何防范dtd/xsd访问外网

做Java的同学估计都碰过类似的故障,就是突然有一天启动应用的时候告诉你由于没获取到dtd或xsd文件应用启动失败,而通常这个时候你才会发现原来自己应用的启动竟然还依赖了外网的某个url,我记得当年ibatis切域名的那一次,就是突然一批机器启动失败,差点造成大故障,还有一次印象深刻的海底光缆断掉的那一次,也是差点引发大故障。

对于上面这样的现象,最好的办法自然是确保本地有相应的dtd/xsd文件,从而避免去外网加载,但为什么要做到这点这么麻烦呢,原因在于dtd/xsd文件到底从哪加载是完全可以由解析XML的程序自行来决定的,于是就悲催了。

在解析XML时可自行实现EntityResolver接口,例如Spring的BeansDtdResolver,在这个类中,Spring会首先从当前类的classloader中来获取相应路径的dtd文件,如找不到则从指定的classloader中获取,仍然找不到的话就返回null,当返回null后,外层的ResourceEntityResolver则会访问相应的url去获取dtd,对于xsd文件的处理也基本类似。

唯一还好的就是基本所有解析xml的程序在对dtd/xsd的处理上,都遵循了先尝试从本地装载,之后再从url装载的先后顺序。

对于这个头疼的问题,我之前见过的解决方法有:
1.将所有的dtd/xsd涉及的域名都解析到本地的一台机器,以避免访问外网的现象出现,这个的问题在于无法做到穷举,因为可能不是你的程序需要解析XML,而是你引用的其他jar中有;
2.禁止服务器访问外网,这样的好处就是问题暴露的比较直接,但可能有些应用确实会有需要访问外网的需求;
3.将xml文件中的dtd/xsd改为本地相对路径,这种一个问题是上面的穷举问题,另一个问题是相对路径到底如何访问其实同样取决于自定义实现的xml解析程序;
4.在解析xml时绕过dtd的验证,如果能保证xml文件格式没问题的话,可以绕掉dtd的验证,但xsd就不行了,xsd文件是必须获取到的,否则xml文件压根就没法解析。

目前我们没采用以上4个方法,在前年的时候由于连续出了好几次dtd/xsd访问不了导致各种应用启动不了的现象,我就花了点时间想办法去解决掉这个问题,当时我的解决思路是上面的4个方法在我们的场景中都不能解决,看起来一个折中的办法就是在测试阶段就发现这个问题,然后强迫应用解决掉,这样就可以确保部署到线上的时候访问的一定是本地。

要在测试阶段发现这个问题怎么做呢,想到的办法是用btrace来跟踪URL.openConnection的调用,如URL的结尾是.dtd或.xsd,那么就在日志中输出错误信息和堆栈信息,启动脚本在检查到错误信息后就让进程退出,这样可以确保应用方必须解决掉这个问题。

这个方法work的是挺好的,只是悲催的是在出现了访问外网dtd/xsd时要解决就不好弄了,通常是先尝试把dtd/xsd下载过来放到classpath下,但有些时候会不work,不work的情况下就只能是根据堆栈信息调试,看看为什么在本地没找到,然后在本地相应的路径下放上这个文件,这种case by case的解决过程是很苦逼的。

欢迎大家爆料你碰到过的这类问题,或你的解决方案。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

《解析xml时如何防范dtd/xsd访问外网》有3个想法

  1. 可以再进一步,东西载下来不放本地,维护一个内网的仓库,应该很快就能满足大部分人的需求

    1. @rabbit
      大部分人是没用的,也许会是因为一个重要应用引用的某个外部的jar加载了外网的dtd/xsd,然后某一天批量重启全挂了,那就悲催大了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*