Tomcat中LegacyCookieProcessor與Rfc6265CookieProcessor

背景

近日有用戶反饋tomcat升級後應用出現了一些問題,出現問題的這段時間內,tomcat從8.0.47升級到了8.5.43。 問題主要分爲兩類:html

  1. cookie寫入過程當中,domain若是以.開頭則沒法寫入,好比.xx.com寫入會報錯,而寫入xx.com則沒問題。
  2. cookie讀取後應用沒法解析,寫入cookie的值採用的是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,下面就來看看這兩種策略到底有哪些不一樣.算法

LegacyCookieProcessor

org.apache.tomcat.util.http.LegacyCookieProcessor主要是實現了標準RFC6265, RFC2109RFC2616.spring

寫入cookie

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

  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中出現以下任一一個字符就會被加上引號再傳輸.tomcat

// 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中會校驗不能以.開頭.bash

解析cookie

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

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

Rfc6265CookieProcessor

寫入cookie

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

  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 官方文檔
相關文章
相關標籤/搜索