RFC异常处理方案总结

基础信息
异常表现形式
  • 在项目User、book中,时不时的会报出类似于以下描述的异常信息:Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
  • 重要说明: 在日志中,此异常属于INFO等级
解决思路
  • A:找到7230和3986中,已定义的字符。根据异常提示字符在RFC 7230和RFC 3986中未被定义
  • B: 找到报此异常的未被定义字符
  • C:搜索此异常的通用解决方案
  • D:阻断未被定义字符的来源:即参数中避免出现报此异常字符
  • E: 确认此异常报出的地方:springboot,Tomcat等
  • F: 放行未被定义字符
  • 此次最终次序: E-A-F
  • 思路解析: 事实表示,抓取具体异常接口难度等级二;定位具体字符异常难度等级一;搜索出的异常的通用解决方案,无效。
可行的具体解决方案
  • 1,将所有GET请求替换为POST请求
  • 2,不再使用Tomcat服务器,改变为springboot内嵌的Jetty
  • 3,在springboot中排除默认Tomcat引入,显示声明使用的Tomcat版本——降低版本(在低版本中,Tomcat未对字符做限制)
  • 4,GET请求统一进行编码
  • 5,配置系统属性tomcat.util.http.parser.HttpParser.requestTargetAllow,赋值为希望放行的字符 ——回到定位出具体异常字符的问题/找出所有不被定义的字符
  • 6,配置内嵌Tomcat的不限制字符属性:relaxedQueryChars 和 relaxedPathChars ——回到定位出具体异常字符的问题/找到所有不被定义的字符
  • 7,更改日志报警的过滤配置,将此INFO异常拦截 ——很沙雕
目前已有方案分析
  • 方案一:不建议,原因1,GET和POST请求,有其特定的请求含义,不建议随便更改,而且因为定位不到具体的请求接口,最后的结果可能是把所有的GET请求都换成了POST请求
  • 方案二:不建议:影响范围巨大,极有可能会带来新的问题
  • 方案三:不建议:改变springboot版本默认Tomcat版本,可能会引起兼容性问题,也可能带回低版本Tomcat的bug或者安全问题
  • 方案四:推荐——从客户端来源考虑: 但只能改变当前版本的请求,而低版本依然可能产生此异常,如在当前版本进行编码,可在一次强升后,解决此异常
  • 方案五:可行:但据源码显示,此处配置只能放行:{}|三个字符,即使在此配置,也需要更改相应源码
  • 方案六:推荐——从后端处理考虑:当前的springboot的版本(主要是springboot版本对应的Tomcat版本)不支持此种修改方式,无法处理relaxedQueryChars配置(但此种方式,也是Tomcat官方认可推荐的方案)
  • 方案七: 额、额、额,不知道说什么
详解方案五
  • 首先配置系统参数

    		System.setProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow","\"<>[]^\\`{|}");
    
  • 详解参数用处和用途

  1. 在Tomcat源码中:org.apache.tomcat.util.http.parser类负责处理请求相关字符校验,在该类的最开始,从系统参数中获取合法字符配置,但紧接着,只对于{}|三种字符放行,这也是为什么之前配置系统参数,却仍然报出RFC异常的原因。原因一:因为最开始只配置了{}|三个字符,但请求中携带的,并不是这三者之一;原因二:后来配置了所有RFC7230和3986不被允许的字符,依然报出异常,因为在此代码中,进一步只对{}|进行放行。 源码如下:
   String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");
        log.info(prop);
        if (prop != null) {
            for (int i = 0; i < prop.length(); i++) {
                char c = prop.charAt(i);
                if (c == '{' || c == '}' || c == '|') {
                    REQUEST_TARGET_ALLOW[c] = true;
                } else {
                    log.warn(sm.getString("httpparser.invalidRequestTargetCharacter",
                            Character.valueOf(c)));
                }
            }
        }
  1. 进一步查看报出异常的代码逻辑,当请求字符中,包含有RFC7230和3986不被定义的字符时,首先判断其是否被允许(上段代码所配置的REQUEST_TARGET_ALLOW数组,当前字符是否为true),如果为false,则设置为不被定义的请求字符,从而在Http11InputBuffer类具体解析字符时,因为方法校验为false,触发iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 异常
            // Not valid for request target.
            // Combination of multiple rules from RFC7230 and RFC 3986. Must be
            // ASCII, no controls plus a few additional characters excluded
            if (IS_CONTROL[i] || i > 127 ||
                    i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||
                    i == '^' || i == '`'  || i == '{' || i == '|' || i == '}') {
                if (!REQUEST_TARGET_ALLOW[i]) {
                    IS_NOT_REQUEST_TARGET[i] = true;
                }
            }
  1. Http11InputBuffer.parseRequestLine中具体用到字符校验的代码段,HttpParser.isNotRequestTarget(chr)如果此方法返回为true,则会报出异常
  2. 找到源码逻辑后,要解决这个问题,就很清晰了。 复制HttpParser类到项目结构中,方法一:直接将RFC7230那一段代码注掉或者改变其逻辑,让其IS_NOT_REQUEST_TARGET不再为true;方法二:改变读取系统参数后的方法,将只针对于{}|三种字符的限制去除
详解方案六
  • 重要说明:用此方法的版本要求,Tomcat8.5.31,对应的springboot版本1.5.13.RELEASE
  • 具体执行——1,springboot1.X配置RFC
@Configuration
public class RFCConfig {
    @Bean
    public EmbeddedServletContainerFactory webServerFactory(){
        TomcatEmbeddedServletContainerFactory factory=new TomcatEmbeddedServletContainerFactory();
        factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
            connector.setAttribute("relaxedQueryChars","[]|{}^`\"<>");
            connector.setAttribute("relaxedPathChars", "[]|");
        });
        return factory;
    }
}
  • 具体执行——2,springboot2.X配置RFC
@Component
public class RFCConfig implements WebServerFactoryCustomizer {


    @Override
    public void customize(WebServerFactory factory) {
        TomcatServletWebServerFactory containerFactory = (TomcatServletWebServerFactory) factory;
        containerFactory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
            connector.setAttribute("relaxedQueryChars", "[]|{}^\"\\<\\>");
            connector.setAttribute("relaxedPathChars", "[]|");
        });
    }
}
  • relaxedQueryChars和relaxedPathChars的起作用的原因为,在高版本的HttpParser类中,改变了其RFC 7230和 3986异常限制的逻辑。其基本改变为,会读取配置中的不加约束字符,如果RFC 7230和3986中不被定义允许的字符,包含在自定义配置的relaxedQueryChars中,则不再限制此字符。具体逻辑,可以看高版本的HttpParser中代码具体实现
最终结论

结合到当前项目使用的相关组件版本情况,决定采用方案五,同时沟通客户端对请求进行编码。

猜你喜欢

转载自blog.csdn.net/u013034889/article/details/97793927