背景
后台将一个token以httpOnly方式写入了浏览器的cookie,在不设置cookie domain的方式下,cookie只能在本域名中使用,例如:
- cookie的domain为"xxx.com",在子域名"yy.xxx.com"下是无法自动带入的
- 因为安全问题设置了httpOnly,script是无法获取这种cookie来带到子域名写入的
而真实场景中,我们常常会用多域名保证主站的稳定访问;子域名用于文件上传,如果子域名无法带入主域名cookie,将无法确认上传者身份。
解决过程
通过查阅以下资料得知,tomcat随着版本的变迁,cookie的处理做过多次更新,最新的tomcat使用的是Rfc6265CookieProcessor,它使用的是白名单的方式来进行cookie的写入,中文是无法写入的,如果有中文可以使用base64编码后,再进行写入。
Tomcat中LegacyCookieProcessor与Rfc6265CookieProcessor)
这些都没有问题,使用Rfc6265CookieProcessor处理cookie的时候,domain设置有了要求,以前使用LegacyCookieProcessor是不要求domain的第一个字符的,现在要求第一个字符不能是.
或者-
,如果以.开头则无法写入,比如.xxx.com
写入会报错,而写入xx.com
则没问题。
而以前的浏览器中,如果domain设置为了.xxx.com
,则子域名可以自动带入主域名的cookie。既然如此,我就按大家说的办,用以下办法设置tomcat使用LegacyCookieProcessor解析器
传统Tomcat
在cont/context.xml中,加入
<Context>
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
</Context>
springboot
@Configuration
public class CookieConfigSupport {
/**
* Solve the problem:
* There was an unexpected error (type=Internal Server Error, status=500).
* An invalid domain [.xxx.com] was specified for this cookie
*
* @return
*/
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
return (factory) -> factory.addContextCustomizers(
(context) -> context.setCookieProcessor(new LegacyCookieProcessor()));
}
}
一个瑕疵
我按照这么设置后,确实domain能加.
了,子域名cookie也能带过来了,因为我的cookie经过了base64编码,发现cookie的值被加了双引号
,没改之前没我是没有双引号的,于是我又花了一天的时间做了以下测试:
- 双引号使用LegacyCookieProcessor/Rfc6265CookieProcessor解析器处理cookie,都能得到正确的值。
- 无引号下生成的cookie,使用LegacyCookieProcessor/Rfc6265CookieProcessor解析器处理cookie,也能得到正确的值。
有网友会问了,你这不是讲废话么?既然都没有问题,那就用旧的解析器就好了~
但我是一个不信邪的人,遇到这种问题,就想办法去处理一下
小插曲
有资料说,在tomcat启动过程中,设置如下参数:
System.setProperty("org.apache.tomcat.util.cookies.invalidSpecial", "false");
System.setProperty("org.apache.tomcat.util.http.ServerCookie.ALLOW_EQUALS_IN_VALUE", "true");
System.setProperty("org.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V0", "true");
System.setProperty("org.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V1", "true");
可以解决引号问题,我试了,在我的环境下是不行的,可能适用于比较旧版本的tomcat
LegacyCookieProcessor
于是我把目光转到这个类上,我把这个类copy出来,将CookieConfigSupport中的处理器换成自己的。把以下代码中的'='
去掉
private static final char[] HTTP_SEPARATORS = new char[] {
'\t', ' ', '\"', '(', ')', ',', ':', ';', '<', '>', '?', '@', '=',
'[', '\\', ']', '{', '}' };
发现写cookie的时候果然去掉了引号,但悲剧是在这种解析器模式下,我的cookie无法正常读取了。
在此问题耗了一整天,薅掉了好些头发,也未解决,主要是水平有限在parseCookieHeader
方法上浪费了不少时间,cookie的解析还包含了其他tomcat的代码,我可不想那么多
Rfc6265CookieProcessor
终于在第二天的时候灵光一闪,既然可以改LegacyCookieProcessor
类,是否也可以改Rfc6265CookieProcessor
,让其支持加'.'
的domain不就好了,说干就干,看了一下源代码,一下就被以下代码吸引住了。
static {
for (char c = '0';c <= '9'; c++) {
domainValid.set(c);
}
for (char c = 'a'; c <= 'z'; c++) {
domainValid.set(c);
}
for (char c = 'A'; c <= 'Z'; c++) {
domainValid.set(c);
}
domainValid.set('.');
domainValid.set('-');
}
看这字面意思,就是domain相关的验证,还有set(“.”),八九不离十,连蒙带骗;把这个给注释了,调试,出现如下错误:
rfc6265CookieProcessor.invalidDomain error
后续发现可能是因为我把文件中jar中移了出来,他找不到原来的properties,直接把这个properties的key给报了出来,反正就是有错误了。
再反复看、调试了一下代码,发现Rfc6265CookieProcessor使用的白名单机制,我把'.'
去掉后,白名单中无法验证了。就直接报错了。于是继续往下看第219/220行,看这个注释,我就知道应该没有问题了,于是我做了如下修改
// labels must start with a letter or number
// 修改前 if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {
if ( prev == -1 && cur == '-') {
最后:
- 将注释还原
- 将220注释的代码,改为上面的代码
- 引号和domain验证的问题解决
总结
- Rfc6265CookieProcessor实现了rfc6265相关的http cookie的规范,使用了更严格的字符和domain校验cookie的合法性
- 为什么一定要使用加
'.'
的方式来设置cookie,因为规范是一个事情,现实是另外一个事情,现实是你无法知道你的用户使用的浏览器是什么年代的,是否符合最新的http规范,如果符合的话,是不需要通过设置domain加'.'
的方式允许子域名访问主域名的cookie的。 - 既然旧的解析器,并不会报错,为什么要去改Rfc6265CookieProcessor源码,两个原因:
- cookie继承原来的书写方式,避免在生产环境中产生未知的影响,因为生产环境中不只有java
- 使用Rfc6265CookieProcessor好处是沿用了白名单机制,只破坏了
'.'
的验证方式,从代码改动上来说,负担是比较小的
- 规范与现实总是存在差距,既然不可调和,那就解决它