spring版本
spring 2.0后使用方法:https://blog.csdn.net/doctor_who2004/article/details/81750713
spring boot 2.0以前使用如下方式
背景
近日有用戶反饋tomcat升級后應用出現了一些問題,出現問題的這段時間內,tomcat從8.0.47
升級到了8.5.43
。 問題主要分為兩類:
- cookie寫入過程中,domain如果以
.
開頭則無法寫入,比如.xx.com
寫入會報錯,而寫入xx.com
則沒問題。 - cookie讀取后應用無法解析,寫入cookie的值采用的是
Base64
算法。
定位
經過一番搜索,發現tomcat在這兩個版本中,cookie的寫入和解析策略確實發生了一些變化,可見Tomcat的文檔,里面有這么一段提示:
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
,下面就來看看這兩種策略到底有哪些不同.


LegacyCookieProcessor
org.apache.tomcat.util.http.LegacyCookieProcessor
主要是實現了標准RFC6265
, RFC2109
和 RFC2616
.
寫入cookie
寫入cookie的邏輯都在generateHeader
方法中. 這個方法邏輯大概是:
- 直接拼接
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中出現如下任一一個字符就會被加上引號再傳輸.
// 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
中會校驗不能以.
開頭.

解析cookie
在這種LegacyCookieProcessor
策略中,對有引號和value和沒有引號的value執行了兩種不同的解析方法.代碼邏輯在processCookieHeader
方法中,簡單來說 1.對於有引號的value,解析的時候value就是兩個引號之間的值.代碼可以參考,主要就是getQuotedValueEndPosition
在處理.

2.對於沒有引號的value.則執行getTokenEndPosition
方法,這個方法如果碰到HTTP_SEPARATORS
中任何一個分隔符,則視為解析完成.
Rfc6265CookieProcessor
寫入cookie
寫入cookie的邏輯和上面類似,只是校驗發生了變化
- 直接拼接
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
解析cookie主要在parseCookieHeader
中,和上面類似,也是對引號有特殊處理,
- 如果有引號,只獲取引號之間的部分,
- 沒有引號的時候會判斷value是否有
括號
,空格
,tab
,如果有,則會會視為結束符.

解釋
再回到文章開始的兩個問題,如果都使用tomcat的默認配置:
- 由於
tomcat8.5
以后都使用了Rfc6265CookieProcessor
,所以domain
只能用xx.com
這種格式. Base64
由於會用=
補全,而=
在LegacyCookieProcessor
會被視為特殊符號,導致Rfc6265CookieProcessor
寫入的cookie沒有引號,LegacyCookieProcessor
在解析value的時候遇到=
就結束了,所以老版本的tomcat無法正常工作,只能獲取到=
前面一截.
解決方法
從以上代碼來看,其實LegacyCookieProcessor
可以讀取Rfc6265CookieProcessor
寫入的cookie.而且Rfc6265CookieProcessor
可以正常讀取LegacyCookieProcessor
寫入額cookie .那么在新老版本交替中,我們把tomcat的的CookieProcessor
都設置為LegacyCookieProcessor
,即可解決所有問題.
如何設置
傳統Tomcat
修改conf
文件夾下面的context.xml
,增加CookieProcessor
配置在Context
節點下面:
<Context> <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" /> </Context> 復制代碼
Spring Boot
對於只讀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()); } }); } } }; } 復制代碼
引用
作者:candyleer
鏈接:https://juejin.im/post/6844903918330183694
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。