Tomcat中LegacyCookieProcessor與Rfc6265CookieProcessor


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。 問題主要分為兩類:

  1. cookie寫入過程中,domain如果以.開頭則無法寫入,比如.xx.com寫入會報錯,而寫入xx.com則沒問題。
  2. 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, RFC2109RFC2616.

寫入cookie

寫入cookie的邏輯都在generateHeader方法中. 這個方法邏輯大概是:

  1. 直接拼接 cookie.getName()然后拼接=.
  2. 校驗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', ' ', '\"', '(', ')', ',', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' }; 復制代碼
  1. 拼接domain字段,如果滿足上面加引號的條件,也會被加上引號.
  2. 拼接Max-AgeExpires.
  3. 拼接Path,如果滿足上面加引號的條件,也會被加上引號.
  4. 直接拼接SecureHttpOnly.

值得一提的是,LegacyCookieProcessor這種策略中,domain可以寫入.xx.com,而在Rfc6265CookieProcessor中會校驗不能以.開頭.

 

 

解析cookie

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

 

 

2.對於沒有引號的value.則執行getTokenEndPosition方法,這個方法如果碰到HTTP_SEPARATORS中任何一個分隔符,則視為解析完成.

Rfc6265CookieProcessor

寫入cookie

寫入cookie的邏輯和上面類似,只是校驗發生了變化

  1. 直接拼接 cookie.getName()然后拼接=.
  2. 校驗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))); } } } 復制代碼

對於碼表如下:

 

  1. 拼接Max-AgeExpires.
  2. 拼接Domain.增加了對domain 的校驗. (domain必須以數字或者字母開頭,必須以數字或者字母結尾)
  3. 拼接Path,path 字符不能為;,不能小於0x20,不能大於0x7e;
  4. 直接拼接SecureHttpOnly.

通過與LegacyCookieProcessor對比可知,Rfc6265CookieProcessor不會對某些特殊字段的value加引號,其實都是因為這兩種策略實現的規范不同而已.

解析cookie

解析cookie主要在parseCookieHeader中,和上面類似,也是對引號有特殊處理,

  1. 如果有引號,只獲取引號之間的部分,
  2. 沒有引號的時候會判斷value是否有括號,空格,tab,如果有,則會會視為結束符.

 

 

解釋

再回到文章開始的兩個問題,如果都使用tomcat的默認配置:

  1. 由於tomcat8.5以后都使用了Rfc6265CookieProcessor,所以domain只能用xx.com這種格式.
  2. 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()); } }); } } }; } 復制代碼

引用

  1. Spring Boot設置
  2. Tomcat 官方文檔

作者:candyleer
鏈接:https://juejin.im/post/6844903918330183694
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM