博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
记一次升级Tomcat
阅读量:6169 次
发布时间:2019-06-21

本文共 10144 字,大约阅读时间需要 33 分钟。

记一次升级Tomcat

总述

    JDK都要出12了,而我们项目使用的jdk却仍然还停留在JDK1.6。为了追寻技术的发展的脚步,我这边准备将项目升级到JDK1.8。而作为一个web项目,我们的容器使用的是Tomcat。看了下Tomcat版本与JDK版本之间的兼容关系以及网上所传的各种JDK1.8和Tomcat7不兼容的问题, 我决定将Tomcat升级到8。我这里本地验证采用的tomcat版本是8.5.38。

问题一:请求js文件报404错误

    其实这个问题严格来讲不是升级到Tomcat8出现的问题,而是升级到Tomcat9出现的问题。正好我开始尝试的是Tomcat9,无法解决这个问题才降到Tomcat8。所以这里一并记录下来。

    这个问题在从Tomcat6升级到Tomcat7之后也会存在,原因如下,在项目代码中对js的请求路径中包含了{、}等特殊符号:

    前台会发现加载js的时候报了404的错误,后台报错信息如下:

Invalid character found in the request target.The valid characters are defined in RFC 7230 and RFC3986

    出现这个问题的原因是因为Tomcat升级之后对安全进行了升级,其中就有对请求中的特殊字符进行校验,具体校验规则参照下面的代码:

(InternalInputBuffer、InternalAprInputBuffer、InternalNioInputBuffer)

/** * Read the request line. This function is meant to be used during the * HTTP request header parsing. Do NOT attempt to read the request body * using it. * * @throws IOException If an exception occurs during the underlying socket * read operations, or if the given buffer is not big enough to accommodate * the whole line. */@Overridepublic boolean parseRequestLine(boolean useAvailableDataOnly)    throws IOException {    int start = 0;    //    // Skipping blank lines    //    byte chr = 0;    do {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        // Set the start time once we start reading data (even if it is        // just skipping blank lines)        if (request.getStartTime() < 0) {            request.setStartTime(System.currentTimeMillis());        }        chr = buf[pos++];    } while ((chr == Constants.CR) || (chr == Constants.LF));    pos--;    // Mark the current buffer position    start = pos;    //    // Reading the method name    // Method name is a token    //    boolean space = false;    while (!space) {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        // Spec says method name is a token followed by a single SP but        // also be tolerant of multiple SP and/or HT.        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {            space = true;            request.method().setBytes(buf, start, pos - start);        } else if (!HttpParser.isToken(buf[pos])) {            throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));        }        pos++;    }    // Spec says single SP but also be tolerant of multiple SP and/or HT    while (space) {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {            pos++;        } else {            space = false;        }    }    // Mark the current buffer position    start = pos;    int end = 0;    int questionPos = -1;    //    // Reading the URI    //    boolean eol = false;    while (!space) {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        // Spec says single SP but it also says be tolerant of HT        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {            space = true;            end = pos;        } else if ((buf[pos] == Constants.CR)                   || (buf[pos] == Constants.LF)) {            // HTTP/0.9 style request            eol = true;            space = true;            end = pos;        } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {            questionPos = pos;        } else if (HttpParser.isNotRequestTarget(buf[pos])) {            throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));        }        pos++;    }    request.unparsedURI().setBytes(buf, start, end - start);    if (questionPos >= 0) {        request.queryString().setBytes(buf, questionPos + 1,                                       end - questionPos - 1);        request.requestURI().setBytes(buf, start, questionPos - start);    } else {        request.requestURI().setBytes(buf, start, end - start);    }    // Spec says single SP but also says be tolerant of multiple SP and/or HT    while (space) {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {            pos++;        } else {            space = false;        }    }    // Mark the current buffer position    start = pos;    end = 0;    //    // Reading the protocol    // Protocol is always "HTTP/" DIGIT "." DIGIT    //    while (!eol) {        // Read new bytes if needed        if (pos >= lastValid) {            if (!fill())                throw new EOFException(sm.getString("iib.eof.error"));        }        if (buf[pos] == Constants.CR) {            end = pos;        } else if (buf[pos] == Constants.LF) {            if (end == 0)                end = pos;            eol = true;        } else if (!HttpParser.isHttpProtocol(buf[pos])) {            // 关键点在这一句,如果校验不通过,则会报参数异常            throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));        }        pos++;    }    if ((end - start) > 0) {        request.protocol().setBytes(buf, start, end - start);    } else {        request.protocol().setString("");    }    return true;}

我们进一步跟进HttpParser中的方法:

public static boolean isNotRequestTarget(int c) {    // Fast for valid request target characters, slower for some incorrect    // ones    try {        // 关键在于这个数组        return IS_NOT_REQUEST_TARGET[c];    } catch (ArrayIndexOutOfBoundsException ex) {        return true;    }}// Combination of multiple rules from RFC7230 and RFC 3986. Must be// ASCII, no controls plus a few additional characters excludedif (IS_CONTROL[i] || i > 127 ||        i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||        i == '^' || i == '`'  || i == '{' || i == '|' || i == '}') {    // 可以看到只有在REQUEST_TARGET_ALLOW数组中的值才不会设置成true,所以我们需要追踪REQUEST_TARGET_ALLOW数组的赋值    if (!REQUEST_TARGET_ALLOW[i]) {        IS_NOT_REQUEST_TARGET[i] = true;    }}String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");if (prop != null) {    for (int i = 0; i < prop.length(); i++) {        char c = prop.charAt(i);        // 可以看到在配置文件中配置了tomcat.util.http.parser.HttpParser.requestTargetAllow并且包含{、}、|的时候,REQUEST_TARGET_ALLOW数组中的值才会为true        if (c == '{' || c == '}' || c == '|') {            REQUEST_TARGET_ALLOW[c] = true;        } else {            log.warn(sm.getString("httpparser.invalidRequestTargetCharacter",                    Character.valueOf(c)));        }    }}

    解决办法: 其实通过源码分析不难得到解决办法

在Tomcat的catalina.properties文件中添加以下语句:

tomcat.util.http.parser.HttpParser.requestTargetAllow={}|

当然需要注意的是,这个后门在Tomcat8.5以后就无法使用的,Tomcat9之后的解决办法暂时未找到,可能只有对URL进行编码了。

问题二:Cookie设置报错

     这个问题就是在升级到Tomcat8.5以上的时候会出现的,具体原因是Tomcat8.5采用的Cookie处理类是:

Rfc6265CookieProcessor,而在之前使用的处理类是LegacyCookieProcessor。该处理类对domai进行了校验:

private void validateDomain(String domain) {    int i = 0;    int prev = -1;    int cur = -1;    char[] chars = domain.toCharArray();    while (i < chars.length) {        prev = cur;        cur = chars[i];        if (!domainValid.get(cur)) {            throw new IllegalArgumentException(sm.getString(                    "rfc6265CookieProcessor.invalidDomain", domain));        }        // labels must start with a letter or number        if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {            throw new IllegalArgumentException(sm.getString(                    "rfc6265CookieProcessor.invalidDomain", domain));        }        // labels must end with a letter or number        if (prev == '-' && cur == '.') {            throw new IllegalArgumentException(sm.getString(                    "rfc6265CookieProcessor.invalidDomain", domain));        }        i++;    }    // domain must end with a label    if (cur == '.' || cur == '-') {        throw new IllegalArgumentException(sm.getString(                "rfc6265CookieProcessor.invalidDomain", domain));    }}

新的Cookie规范对domain有以下要求

1、必须是1-9、a-z、A-Z、. 、- (注意是-不是_)这几个字符组成

2、必须是数字或字母开头 (所以以前的cookie的设置为.XX.com 的机制要改为 XX.com 即可)
3、必须是数字或字母结尾

原来的代码设置domain时如下:

cookie.setDomain(".aaa.com");

这就导致设置domain的时候不符合新的规范,直接报错如下:

java.lang.IllegalArgumentException: An invalid domain [.aaa.com] was specified for this cookie        at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateDomain(Rfc6265CookieProcessor.java:181)        at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:123)        at org.apache.catalina.connector.Response.generateCookieString(Response.java:989)        at org.apache.catalina.connector.Response.addCookie(Response.java:937)        at org.apache.catalina.connector.ResponseFacade.addCookie(ResponseFacade.java:386)

    解决办法(以下3中任意一种皆可)

  1. 修改原来代码为:

    cookie.setDomain("aaa.com");
  2. 如果是Spring-boot环境,直接替换默认的Cookie处理类:

    @Configuration@ConditionalOnExpression("${tomcat.useLegacyCookieProcessor:false}")public class LegacyCookieProcessorConfiguration {    @Bean    EmbeddedServletContainerCustomizer embeddedServletContainerCustomizerLegacyCookieProcessor() {        return new EmbeddedServletContainerCustomizer() {            @Override            public void customize(ConfigurableEmbeddedServletContainer factory) {                if (factory instanceof TomcatEmbeddedServletContainerFactory) {                    TomcatEmbeddedServletContainerFactory tomcatFactory =                            (TomcatEmbeddedServletContainerFactory) factory;                    tomcatFactory.addContextCustomizers(new TomcatContextCustomizer() {                        @Override                        public void customize(Context context) {                            context.setCookieProcessor(new LegacyCookieProcessor());                        }                    });                }            }        };    }}
  3. 在Tomcat的context.xml中增加如下配置,指定Cookie的处理类:

参考链接

原文地址

转载地址:http://pdnba.baihongyu.com/

你可能感兴趣的文章
时间戳前
查看>>
11月22日晚上海交大《PMI敏捷实践指南解读》线上沙龙欢迎你的参与!
查看>>
初识 Linux (VMware、CentOS 7)
查看>>
使用SpringMVC完成文件上传
查看>>
mysql Load Data InFile 的用法
查看>>
Go new vs make
查看>>
【云宏大讲坛】超融合,融合的不仅是基础架构
查看>>
pytnon入门的一些小实例
查看>>
ubuntu下的dock工具
查看>>
饿了么被上海市市场监督局予以警告处分
查看>>
Java项目读取配置文件时,找不到指定的文件???
查看>>
lua/luajit and tcc
查看>>
前端安全即JS代码安全,前端源码安全探讨!
查看>>
如何快速实现异地不同网络打印机共享
查看>>
openinstall免费服务对App推广有哪些作用?
查看>>
基于Docker的微服务CI CD流水线
查看>>
学好SEO需要掌握哪些知识要点?
查看>>
JetBrains GoLand macv2019.1.2中文版如何换成无牵引模式?
查看>>
电气火灾监控系统工作原理
查看>>
中使馆驳斥《金融时报》“中国网络威胁论”
查看>>