TOMCAT 8 COOKIEPROCESSOR 实现变化

/ dev

问题

Tomcat 从7升级到8的时候出现了 java.lang.IllegalArgumentException: An invalid domain [.xxx.com] was specified for this cookie 异常。

配置中有这么段配置:

1
<Context useHttpOnly="true" sessionCookiePath="/" sessionCookieDomain=".xxx.cn" />

解决

在tomcat context.xml中配置:

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

即可,或者你也可以把域名前面的.去掉。

分析

Tomcat 8更换了默认的 CookieProcessor 实现为 Rfc6265CookieProcessor,之前的实现为LegacyCookieProcessor。前者是基于 RFC6265,而后者基于RFC6265、RFC2109、RFC2616。

RFC6265中关于domain有这么一段描述:

Domain=domain
Optional. The Domain attribute specifies the domain for which the
cookie is valid. An explicitly specified domain must always start
with a dot.

而在RFC6265中:

If the attribute-name case-insensitively matches the string “Domain”,
the user agent MUST process the cookie-av as follows.

If the attribute-value is empty, the behavior is undefined. However,
the user agent SHOULD ignore the cookie-av entirely.

If the first character of the attribute-value string is %x2E (“.”):

Let cookie-domain be the attribute-value without the leading %x2E
(“.”) character.

Otherwise:

Let cookie-domain be the entire attribute-value.

Convert the cookie-domain to lower case.

一个说要.一个说不要,那再来看看具体代码实现(generateHeader方法)。

LegacyCookieProcessor(省略了部分代码)

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public String generateHeader(Cookie cookie) {
......
String domain = cookie.getDomain();
......
// Add domain information, if present
if (domain != null) {
buf.append("; Domain=");
maybeQuote(buf, domain, version);
}
......
}

再看看maybeQuote方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}

可以看到并没有做任何domain校验的操作;
Rfc6265CookieProcessor

1
2
3
4
5
6
7
8
9
10
11
@Override
public String generateHeader(Cookie cookie) {
......
String domain = cookie.getDomain();
if (domain != null &amp;&amp; domain.length() &gt; 0) {
validateDomain(domain);
header.append("; Domain=");
header.append(domain);
}
......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void validateDomain(String domain) {
int i = 0;
int prev = -1;
int cur = -1;
char[] chars = domain.toCharArray();
while (i &lt; 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
// RFC6265实现
if ((prev == '.' || prev == -1) &amp;&amp; (cur == '.' || cur == '-')) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
// labels must end with a letter or number
// RFC6265实现
if (prev == '-' &amp;&amp; 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));
}
}

validateDomain方法中实现了RFC6265,这蛋疼的标准改动还真是随意。

参考文档:
https://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html
https://tools.ietf.org/html/rfc2109
https://tools.ietf.org/html/rfc6265#section-5.2.3
LegacyCookieProcessor源码
Rfc6265CookieProcessor源码