zoukankan      html  css  js  c++  java
  • 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 != '	') || 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 = "()<>@,;:\"/[]?={} 	";
    private static final char[] HTTP_SEPARATORS = new char[] {
                '	', ' ', '"', '(', ')', ',', ':', ';', '<', '=', '>', '?', '@',
                '[', '\', ']', '{', '}' };
    复制代码
    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
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    设计模式—适配器模式
    设计模式—策略模式 状态模式
    设计模式——装饰模式和代理模式
    C++常考算法
    ModelState.AddModelError使用
    Json
    ref与out
    三层与mvc
    新的方法(Set<T>)实现mvc的crud
    【程序45】
  • 原文地址:https://www.cnblogs.com/soso_ak/p/13898019.html
Copyright © 2011-2022 走看看