近日有用户反馈tomcat升级后应用出现了一些问题,出现问题的这段时间内,tomcat从8.0.47
升级到了8.5.43
。 问题主要分为两类:html
.
开头则没法写入,好比.xx.com
写入会报错,而写入xx.com
则没问题。Base64
算法。通过一番搜索,发现tomcat在这两个版本中,cookie的写入和解析策略确实发生了一些变化,可见Tomcat的文档,里面有这么一段提示:java
The standard implementation of CookieProcessor is org.apache.tomcat.util.http.LegacyCookieProcessor. Note that it is anticipated that this will change to org.apache.tomcat.util.http.Rfc6265CookieProcessor in a future Tomcat 8 release.
复制代码
因为8.0
事后就直接到了8.5
,从8.5
开始就默认使用了org.apache.tomcat.util.http.Rfc6265CookieProcessor
,而以前的版本中一直使用的是org.apache.tomcat.util.http.LegacyCookieProcessor
,下面就来看看这两种策略到底有哪些不一样.算法
org.apache.tomcat.util.http.LegacyCookieProcessor
主要是实现了标准RFC6265
, RFC2109
和 RFC2616
.spring
写入cookie的逻辑都在generateHeader
方法中. 这个方法逻辑大概是:apache
cookie.getName()
而后拼接=
.cookie.getValue()
以肯定是否须要为value
加上引号.private void maybeQuote(StringBuffer buf, String value, int version) {
if (value == null || value.length() == 0) {
buf.append("\"\"");
} else if (alreadyQuoted(value)) {
buf.append('"');
escapeDoubleQuotes(buf, value,1,value.length()-1);
buf.append('"');
} else if (needsQuotes(value, version)) {
buf.append('"');
escapeDoubleQuotes(buf, value,0,value.length());
buf.append('"');
} else {
buf.append(value);
}
}
private boolean needsQuotes(String value, int version) {
...
for (; i < len; i++) {
char c = value.charAt(i);
if ((c < 0x20 && c != '\t') || c >= 0x7f) {
throw new IllegalArgumentException(
"Control character in cookie value or attribute.");
}
if (version == 0 && !allowedWithoutQuotes.get(c) ||
version == 1 && isHttpSeparator(c)) {
return true;
}
}
return false;
}
复制代码
只要cookie value中出现以下任一一个字符就会被加上引号再传输.tomcat
// separators as defined by RFC2616
String separators = "()<>@,;:\\\"/[]?={} \t";
private static final char[] HTTP_SEPARATORS = new char[] {
'\t', ' ', '\"', '(', ')', ',', ':', ';', '<', '=', '>', '?', '@',
'[', '\\', ']', '{', '}' };
复制代码
domain
字段,若是知足上面加引号的条件,也会被加上引号.Max-Age
和Expires
.Path
,若是知足上面加引号的条件,也会被加上引号.Secure
和HttpOnly
.值得一提的是,LegacyCookieProcessor
这种策略中,domain
能够写入.xx.com
,而在Rfc6265CookieProcessor
中会校验不能以.
开头.bash
在这种LegacyCookieProcessor
策略中,对有引号和value和没有引号的value执行了两种不一样的解析方法.代码逻辑在processCookieHeader
方法中,简单来讲 1.对于有引号的value,解析的时候value就是两个引号之间的值.代码能够参考,主要就是getQuotedValueEndPosition
在处理.cookie
2.对于没有引号的value.则执行getTokenEndPosition
方法,这个方法若是碰到HTTP_SEPARATORS
中任何一个分隔符,则视为解析完成.app
写入cookie的逻辑和上面相似,只是校验发生了变化dom
cookie.getName()
而后拼接=
.cookie.getValue()
,只要没有特殊字段就经过校验,不会额外为特殊字符加引号.private void validateCookieValue(String value) {
int start = 0;
int end = value.length();
if (end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"') {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c || c == 0x7f) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidCharInValue", Integer.toString(c)));
}
}
}
复制代码
对于码表以下:
Max-Age
和Expires
.Domain
.增长了对domain 的校验. (domain必须以数字或者字母开头,必须以数字或者字母结尾)Path
,path 字符不能为;
,不能小于0x20
,不能大于0x7e
;Secure
和HttpOnly
.经过与LegacyCookieProcessor
对比可知,Rfc6265CookieProcessor
不会对某些特殊字段的value加引号,其实都是由于这两种策略实现的规范不一样而已.
解析cookie主要在parseCookieHeader
中,和上面相似,也是对引号有特殊处理,
括号
,空格
,tab
,若是有,则会会视为结束符.再回到文章开始的两个问题,若是都使用tomcat的默认配置:
tomcat8.5
之后都使用了Rfc6265CookieProcessor
,因此domain
只能用xx.com
这种格式.Base64
因为会用=
补全,而=
在LegacyCookieProcessor
会被视为特殊符号,致使Rfc6265CookieProcessor
写入的cookie没有引号,LegacyCookieProcessor
在解析value的时候遇到=
就结束了,因此老版本的tomcat没法正常工做,只能获取到=
前面一截.从以上代码来看,其实LegacyCookieProcessor
能够读取Rfc6265CookieProcessor
写入的cookie.并且Rfc6265CookieProcessor
能够正常读取LegacyCookieProcessor
写入额cookie .那么在新老版本交替中,咱们把tomcat的的CookieProcessor
都设置为LegacyCookieProcessor
,便可解决全部问题.
修改conf
文件夹下面的context.xml
,增长CookieProcessor
配置在Context
节点下面:
<Context>
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
</Context>
复制代码
对于只读cookie不写入的应用来讲,没必要修改,若是要修改,能够增长以下配置便可.
@Bean
public EmbeddedServletContainerCustomizer cookieProcessorCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container)
.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
context.setCookieProcessor(new LegacyCookieProcessor());
}
});
}
}
};
}
复制代码