总述

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

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

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

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

<script type="text/javascript" src="${ctx}/js/common/include_css.js?{'ctx':'${ctx}','easyui':'easyui'}"></script>

    前台会发现加载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.
*/
@Override
public 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 excluded
if (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的处理类:

    <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />

参考链接

https://blog.csdn.net/fy_sun123/article/details/73115381

http://ju.outofmemory.cn/entry/367186

https://www.cnblogs.com/lr393993507/p/7755867.html

http://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html

记一次升级Tomcat的更多相关文章

  1. Tomcat8.5 升级tomcat版本导致出现异常,Base64不存在

    Tomcat8.5 升级tomcat版本导致出现异常,Base64不存在 原因分析: 由于tomcat由7升级到8.5导致Base64的引用路径错误,默认引用为8.5中的jar, 解决方案: 修改引用 ...

  2. 害你加班的bug就是我写的,记一次升级Jenkins插件引发的加班

    主旨 本文主要记录了下Jenkins升级插件过程中出现的场景,一次加班经历,事发时没有截图,有兴趣可以看看. 起因 需求 最近有个需求:在Jenkins流水线中完成下载Git上的文件简单修改并提交的功 ...

  3. 记一次在Tomcat部署项目后无法启动该项目的例子

    在一次正常部署后,发现在Tomcat的管理中无法将该项目启动起来,并在Tomcat的日志文件localhost.log中发现以下错误信息: java.lang.NoSuchMethodError: o ...

  4. 记一次服务器Tomcat优化经历

    公司需要一台测试服务器来做测试用,所以花了几天时间把服务全部部署好,在部署好war包之后,发现Tomcat访问超级慢. 1.进入Tomcat的bin目录下,运行 ./catalina.sh run命令 ...

  5. 升级tomcat需要更改哪些配置?

    1.上传Tomcatapache-tomcat-7.0.84.zip将38服务器上的Tomcat传到107服务器指定目录:scp /data/apache-tomcat-7.0.84.zip jsdx ...

  6. 记一次升级Ubuntu内核

      uname -a 查看当前使用内核版本 升级指定内核  apt-get install linux-image-4.4.0-131-generic dpkg --get-selections | ...

  7. 记一次排查tomcat耗费CPU过高的经历

    有一个新项目,在测试环境部署后,发现tomcat进程耗费的CPU非常高,排查过程如下: 日志搜集 先通过top,查找耗费CPU最高的线程 top -Hp pid 将线程ID转为16进制 printf ...

  8. 记一次解决tomcat自动关闭的bug

    最近一个运行了4年的javaee web项目,经常接到客户反馈系统无法打开.登录服务器查看服务,发现是tomcat自动关闭了.基本是3到4天发生一次. 运维人员开始以为是其他服务杀死了tomcat服务 ...

  9. 记一次启动Tomcat 控制台以及log4j 乱码问题

    Tomcat启动乱码 问题描述:当你发现你的Tomcat启动时乱码了,而你只是换了个Tomcat版本而已. 在找到真正的问题之前,我在网上百度了N多的资料,都试过了,但是都不行.1.修改了 windo ...

随机推荐

  1. c++沉思录 学习笔记 第五章 代理类

    Vehicle 一个车辆的虚基类 class Vehicle {public: virtual double weight()const = 0; virtual void start() = 0; ...

  2. SPARK安装一:Windows下VirtualBox安装CentOS

    一.虚拟机安装 重点是网络设置,参见:https://www.linuxidc.com/Linux/2018-04/151924.htm 本文用三台2核4g虚拟机做集群,虚拟机安装centos7,如下 ...

  3. 在Linux服务器上配置Transmission来离线下载BT种子

    Transmission简介 Transmission是一种BitTorrent客户端,特点是跨平台的后端和简洁的用户界面,硬件资源消耗极少,支持包括Linux.BSD.Solaris.Mac OS ...

  4. [uboot] (第三章)uboot流程——uboot-spl代码流程

    http://blog.csdn.net/ooonebook/article/details/52957395 以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为 ...

  5. 数据库-mysql语句-查-WEB服务器

    (1)MySQL中的查询 (2)WEB服务器 Order:  订单.排序 Set:设置.集合 Table:表.桌子 1.MySQL中的简单查询 —— 查询结果的排序 示例:查询出所有员工信息,要求按工 ...

  6. SimpleCursorAdapter和ListView的结合使用

    我们在用SQLite查数据的时候,经常会用到Cursor这个游标,我们希望能将游标指向的数据直接绑定到ListView中,这样就免去了将游标数据取出然后转换到SimpleAdapter中的麻烦.今天我 ...

  7. 说说Runnable与Callable

    Callable接口: public interface Callable<V> { V call() throws Exception; } Runnable接口: public int ...

  8. 如何将已有的本地Git 库推送到远端仓库?

    以Github 为例 step 1. 在Github建立一个空的仓库 Step 2. 建立远端仓库的别名 >$ git remote add origin https://github.com/ ...

  9. NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成

    本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过. 快一个月没有写博文了,最近忙着两件事;    一:阅读刘墉先生的<说话的魅力>,以一种微妙的,你我大家都会经常遇见 ...

  10. .NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(二)

    重温Consul最少化集群的搭建