Nginx log error:client sent invalid userid cookie
基于日志的统计分析按日志来源一般分为后端 cgi、app 日志和前端 js 挂码日志,其中前端 js 挂码由于与具体后端业务逻辑低耦合、异步加载等特性,使得其在网站统计分析领域应用广泛。
今天就来看一个 nginx 日志收集过程中的 case。
最近在 review nginx 配置的时候,发现 nginx 每天会有 1% 的 errlog,由于公司的业务访问量还算比较大的,算下来这 1% 也不是个小数目,有必要搞清楚这 1% 究竟怎么产生的。
1、错误日志样式:
错误日志的样式大致分为两种,如下:
2014/07/03 00:06:51 [error] 30605#0: *15901655967 client sent invalid userid cookie "cookieuid1=05dvUVObOC+UGCrSG4gWAg==; jobbest_cateid=38676; isfirst=true; showcountdown=true; stopnotice=true; _TCN=4FD2E673D11B18C5060BF413BB796EB5; idooxx="05dz8VO0Lew1TT66I0MUAg=="; ...
2014/08/13 11:01:25 [error] 13702#0: *19402474334 open() "/opt/web/tracklog.ooxx.com.static/mooxx/m3/js/m.lazyload.js" failed (2: No such file or directory), client: 42.249.142.200, server: tracklog.ooxx.com, ...
前者在整个 errlog 中占比 99%,后者 1% 左右,前者就是今天要讨论的主题:为什么 nginx 会报这种错误,而后者这种错误一般是原本的访问路径不正确或者运营商劫持导致访问路径错误。
2、按图索骥:track nginx source code
搜了下,网上貌似很少有人问到这个问题,即使问到了也貌似没有明确的解答,当 STFW 和 RTFM 都不管用的时候,那就只有硬着头皮看源码了,按图索骥,看看源码中,何处抛出的 client sent invalid userid cookie 这个错误:
static ngx_http_userid_ctx_t *
ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
{
...
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"uid cookie: "%V"", &ctx->cookie);
if (ctx->cookie.len < 22) {
cookies = r->headers_in.cookies.elts;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent too short userid cookie "%V"",
&cookies[n]->value);
return ctx;
}
src = ctx->cookie;
/*
* we have to limit the encoded string to 22 characters because
* 1) cookie may be marked by "userid_mark",
* 2) and there are already the millions cookies with a garbage
* instead of the correct base64 trail "=="
*/
src.len = 22;
dst.data = (u_char *) ctx->uid_got;
if (ngx_decode_base64(&dst, &src) == NGX_ERROR) {
cookies = r->headers_in.cookies.elts;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client sent invalid userid cookie "%V"",
&cookies[n]->value);
return ctx;
}
...
}
ngx_int_t
ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src)
{
static u_char basis64[] = {
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
};
return ngx_decode_base64_internal(dst, src, basis64);
}
static ngx_int_t
ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
{
...
for (len = 0; len < src->len; len++) {
if (src->data[len] == '=') {
break;
}
if (basis[src->data[len]] == 77) {
return NGX_ERROR;
}
}
if (len % 4 == 1) {
return NGX_ERROR;
}
s = src->data;
d = dst->data;
while (len > 3) {
*d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
*d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
*d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
s += 4;
len -= 4;
}
...
return NGX_OK;
}
可以看到,源码中会对传入的 cookieId 做 base64 合法性校验,如果没有通过校验,则会抛出 client sent invalid userid cookie 错误,并按 HttpUseridModule 的逻辑重新分配新的 cookieId。
3、测试验证
为了证明上述推断的正确性,并解决上述的错误,我们只需要找出没通过校验的地方,并构造测试用例验证即可。
我们取一条正常的请求中的 cookie:
idooxx=05dvZ1ODTpI+FjiILHYwAg==; __ag_cm_=1408028234222; __utma=253535702.161ooxx51834.1401114262.1408099300.1408187334.7; __utmz=253535702.1401114262.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _bu=201104111325386d88c794; city=bj; ooxxhome=bj; myfeet_tooltip=end; new_session=1; init_refer=; ipcity=bj%7C%u5317%u4EAC; __utmb=253535702.3.9.1408187335363; __utmc=253535702
用上述正确的对比错误的 cookie,可以看到,cookieId(idooxx) 中包含了双引号,而导致 cookieId 没有通过 base64 合法性校验的可能正是包含了 非 base64 编码字符串。下面我们来验证下,手动在浏览器中构造一个不合法 cookie,看看是不是有同样的问题。
由于 base64 编码只包含 64 个字符:大小写52 + 数字10 + 2个额外字符+/ 一共64个字符。如果我们客户端发送的 cookieId 中包含了上述非 64 字符集中的字符,那么 nginx HttpUseridModule 模块就会校验后认为请求非法,并会重新非配 cookieId。
4、解决方案
原因找到了,要解决起来就不难了,其实分析这些错误有个共性,就是这些错误都是由移动端产生的,pc 端基本没有,因为 pc 端的浏览器请求的参数都是规范的,而移动端的很多都是 RD 自己拼接然后发送的,难免会不符合规范,有则改之。
5、Refer:
[1] nginx-1.7.3/src/http/modules/ngx_http_userid_filter_module.c
http://lxr.nginx.org/source/src/http/modules/ngx_http_userid_filter_module.c
[2] nginx-1.7.3/src/core/ngx_string.c
http://lxr.nginx.org/source/src/core/ngx_string.c
[3] Errors using HttpUseridModule
http://www.serverphorums.com/read.php?5,856195
[4] Base64编码/解码器
[5] ngx_http_userid_module模块基本指令整理
http://www.iigrowing.cn/ngx-http-userid-module-mo-kuai-ji-ben-zhi-ling-zheng-li.html
- demo3同通讯录展示的方式分组排序
- Android手势研究(textview及listview对比验证)
- demo2动态加载显示商品详情页
- demo1 动态显示view或弹框 动态隐藏view或弹框
- ios 继承UITableViewController,更改tableview样式
- demo1 动态显示view或弹框 动态隐藏view或弹框
- 从零开始的Spring Security Oauth2(一)
- 细说Android事件传递
- swift基础_ set get方法 理解
- 高仿今日头条(2)
- ios tableview 上加 textfiled
- Spring Cloud实战小贴士:Feign的继承特性(伪RPC模式)
- 仿淘宝购买详情页购买缩小动画
- 高仿今日头条(1)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LNMP部署及HTTPS服务开启教程
- 在 Linux 上锁定虚拟控制台会话的实现方法
- Linux中split大文件分割和cat合并文件详解
- centos下安装配置phpMyAdmin的方法步骤
- ubuntu中python调用C/C++方法之动态链接库详解
- linux下cat命令连接文件并打印到标准输出设备上
- 详解Linux误删用户家目录的恢复方法
- Linux下sshd服务及服务管理命令详解
- 关于CentOS 7下sqlite3找不到的问题解决
- 宝塔Linux面板之好用免费的中文Linux VPS主机控制面板适合快速建站
- Linux中openssl/opensslv.h找不到问题的解决方法
- CentOS7 安装 PostgreSQL11的方法步骤
- Linux中FTP服务器的搭建教程
- ubuntu下编译安装opencv的方法
- CentOS7如何执行PHP定时任务详解