近日有用戶反饋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());
}
});
}
}
};
}
複製代碼