nginx userid到底做了啥?

我们公司在用nginx的userid模块作为简单的用户请求追踪使用。这个模块其实并不能真正记录用户的请求状态,只能作为一个辅助使用。但是在一些场景下会有一些异常。下面我们简单介绍一下这个模块到底做了什么。

userid 模块简介

官网说明文档
ngx_http_userid_module

官网示例

userid         on;
userid_name    uid;
userid_domain  example.com;
userid_path    /;
userid_expires 365d;
userid_p3p     'policyref="/w3c/p3p.xml", CP="CUR ADM OUR NOR STA NID"';
配置说明
userid on |v1 | log | off;userid开关
userid_name uid;userid (cookie)名
userid_domain example.com;userid (cookie) domain
userid_path /;userid (cookie) 路径
userid_expires 365d;userid (cookie) 过期时间
userid_p3p ‘policyref=“/w3c/p3p.xml”, CP=“CUR ADM OUR NOR STA NID”’;p3p header 标记

简单来说这个模块的作用就是当客户端的请求cookie中,未携带userid字段,或者userid字段不合法时,nginx在response中会加一个Set-Cookie 的 header。如果配置了p3p,会额外返回p3p的header

set-cookie: uid=CrINEGWBDAFNOTILCEHMAg==; expires=Thu, 18-Dec-25 03:20:33 GMT; domain=example.com; path=/
p3p: policyref="/w3c/p3p.xml", CP="CUR ADM OUR NOR STA NID"

这样同一个客户端将会获得相同的uid,可以作为用户请求追踪的请求特征。但是要注意的是这个cookie的设置逻辑很简单,并且没有用户的登录态吧,所以并不可靠。如果用户使用不同浏览器或者无痕访问就会获得不同的uid,通过他来进行uv等数据统计,获得的结果会虚高。

nginx官网对userid模块的介绍比较简单,我们可以看下他的源码来分析一下他的生成和校验逻辑细节。

我们以文章发布时候最新的1.24版本的nginx源码为例

nginx github路径

userid filter核心函数

nginx userid 是一个 http filter 模块,请求进来后通过调用 ngx_http_userid_filter 这个函数来执行 userid的逻辑,ngx_http_userid_filter这个函数主要调用了 ngx_http_userid_get_uid 和 ngx_http_userid_set_uid。分别用于获取和生成userid

userid的生成逻辑

我们先看下ngx_http_userid_get_uid 这个获取uid的函数。我节选一些核心代码

static ngx_http_userid_ctx_t *
ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf)
{ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);...cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie,&conf->name, &ctx->cookie);if (cookie == NULL) {return ctx;}ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"uid cookie: \"%V\"", &ctx->cookie);if (ctx->cookie.len < 22) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"client sent too short userid cookie \"%V\"",&cookie->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) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"client sent invalid userid cookie \"%V\"",&cookie->value);return ctx;}ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"uid: %08XD%08XD%08XD%08XD",ctx->uid_got[0], ctx->uid_got[1],ctx->uid_got[2], ctx->uid_got[3]);return ctx;
}

首先通过 ngx_http_parse_multi_header_lines 查找cookie中 uid的字段值,存到ctx的结构体中。

 cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie,&conf->name, &ctx->cookie);

ngx_http_parse_multi_header_lines这个函数虽然叫分析header,但是我看了下他的代码实现更像是解析cookie的。它传入3个参数,存放header(其实是cookie,如果请求中有多个cookie header字段,那么就会对应多个数组元素)的数组,cookie字段名的字符串,以及要将查找出来的字符串存放到的位置。返回值cookie字段所在的header数组的index,没查到则返回 NGX_DECLINED,是一个负值。这个函数的返回值在这里没啥太大作用。

拿到 uid之后,就做了两个简单的操作,一个是长度是否小于22,另一是base64解码,解码的时候只会取uid的前22个字符,所以只要前22个字符合法就可以,并存到ctx->uid_got。

异常的话分别会打error log client sent too short userid cookie 或者 client sent invalid userid cookie 。

生成uid

ngx_http_userid_set_uid会先通过调用 ngx_http_userid_create_uid来生成uid。

ngx_http_userid_create_uid会将uid的四个int数据存到 ctx->uid_set中,uid_set和uid_got一样都是一个长度为4的int数组,如果ctx->uid_got已经有数据了,就会直接复制到uid_set中。

如果uid_got中没有的话就会生成uid。根据配置中的userid的on和v1的区别,生存逻辑略有不同。v1的生成逻辑比较简单。

	        if (conf->service == NGX_CONF_UNSET) {ctx->uid_set[0] = 0;} else {ctx->uid_set[0] = conf->service;}ctx->uid_set[1] = (uint32_t) ngx_time();ctx->uid_set[2] = start_value;ctx->uid_set[3] = sequencer_v1;sequencer_v1 += 0x100;

uid_set[0] 是个固定值,uid_set[2]每个worker是固定的。

默认的on的逻辑稍微复杂一些,比如uid_set[0]使用了监听连接地址。但是总得来看他们的生成逻辑差不太多,如果你一直使用同一个nginx,同一个worker接收请求,会发现生成出来的uid有很多位是一直不变的。uid_set[1] 和 uid_set[3]分别是nginx的当前时间和一个计数器,uid的生成更接近一个顺序增加产生的,由于里面包含时间信息,几乎不用担心uid冲突。

uid 信息提取

根据上面的生成逻辑,我们可以知道nginx userid 模块生成的cookie是有服务端地址和生成时间的,我们可以写一个简单的脚本来分析这个cookie。 下面是一段python3代码

import base64
import datetimeclass CookieUID(object):def __init__(self, cookie_uid):self.cookie_uid = cookie_uidself.b_cookie_uid = b''self.check_and_b64decode()def check_and_b64decode(self):if len(self.cookie_uid) != 22 and len(self.cookie_uid) != 24:raise ValueError('cookie uid 的长度需要时22或者24')if len(self.cookie_uid) == 22:self.cookie_uid += '=='elif self.cookie_uid[-2:] != '==':raise ValueError('24字节的cookie_uid 需要以 == 结尾')self.b_cookie_uid = base64.b64decode(self.cookie_uid)def print_info(self):self.print_server_addr()self.print_generated_date()def print_server_addr(self):print('server_addr: ', end='')for i in range(4):print(self.b_cookie_uid[i], end='')if i < 3:print('.', end='')else:print('')def print_generated_date(self):generated_timestamp = int.from_bytes(self.b_cookie_uid[4:8])print('cookie uid generate time: ', datetime.datetime.fromtimestamp(generated_timestamp))if __name__ == '__main__':cookie_uid = CookieUID('fwAAAWWFOcoflzElAwMGAg==')

输出结果是

server_addr: 127.0.0.1
cookie uid generate time:  2023-12-22 15:24:58

写入uid

ngx_http_userid_set_uid 调用完生成userid_create_uid 之后就进行生产cookie的操作。
他会先计算一下将要生产的cookie长度,然后申请一块内存。

cookie = ngx_pnalloc(r->pool, len);

然后将要生成的cookie数据写入或拷贝到cookie的内存中,第一段写入的就是userid对应的cookie

    p = ngx_copy(cookie, conf->name.data, conf->name.len);*p++ = '=';if (ctx->uid_got[3] == 0 || ctx->reset) {src.len = 16;src.data = (u_char *) ctx->uid_set;dst.data = p;ngx_encode_base64(&dst, &src);p += dst.len;if (conf->mark) {*(p - 2) = conf->mark;}} else {p = ngx_cpymem(p, ctx->cookie.data, 22);*p++ = conf->mark;*p++ = '=';}

他会先检查之前ctx->uid_got有没有获取到数据,有的话就直接拷贝之前存在ctx->cookie的数据,并且只会拷贝22个字符。没有的话,就通过之前create生成到ctx->uid_set中的字节通过base64变成成字符串。之后会写入一写其他cookie字段,比如配置中配的domain之类的。

最后通过 ngx_list_push申请header的链表节点结构体,将value指向之前生成的cookie数据上。

	set_cookie = ngx_list_push(&r->headers_out.headers);set_cookie->hash = 1;ngx_str_set(&set_cookie->key, "Set-Cookie");set_cookie->value.len = p - cookie;set_cookie->value.data = cookie;

p3p因为是一个单独的header,所以他也是通过 ngx_list_push 这种方式新增一个header节点。

写到这里其实有个疑问,按照这个模块的逻辑,不管之前请求中是否携带userid,响应头中都会进行set-cookie的操作,这个跟我们实际的现象不太相符。实际中如果有合法的userid cookie,nginx响应头不会再次进行返回set-cookie的header了,这需要后续仔细看下。

uid的插入时机

然后我们在使用中遇到一个问题是,nginx生成的uid是否能通过某些手段控制他的生成呢?比如满足某些情况通过add_header 将其set-cookie置空。这就涉及到nginx模块的执行循序问题。

nginx的header模块执行顺序是通过一个单向链表来实现,每个模块在初始化的时候,会将自己放到链表的头部

	static ngx_int_tngx_http_userid_init(ngx_conf_t *cf){ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_userid_filter;return NGX_OK;}

nginx在处理请求时会遍历这个链表,依次执行对应的filter模块。所以模块初始化的逆序就是各个filter模块的执行顺序。而模块的初始化是在nginx编译的时候进行的,所以可以通过configure生成的ngx_modules.c的顺序来判断filter模块执行顺序。还是以add_header 和 userid为例。add_header属于ngx_http_header_filter_module,userid属于ngx_http_userid_filter_module。
在这里插入图片描述
userid在add_header(ngx_http_userid_filter_module)的上面,执行顺序是先执行add_header再执行userid。由于这两个都控制header的filter,所以按照优先级来看userid的优先级更高。

结语

以上就是全部内容了。这个简单的nginx http filter模块依然涉及很多nginx内部的框架逻辑,大部分都是自己阅读的,难免会有纰漏,恳请各位大佬斧正~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Course Shell of Missing Semester(一)

〇、前言 本文是 The Missing Semester of Your CS Education 课程的课后题答案&#xff0c;课程网站点击这里&#xff0c;以后系列文章不再描述前言。 本文实验环境&#xff1a;阿里云Ubuntu 22.04 Course shell 1、本课程需要使用类Unix shell&#xff0c;例如 Bash 或 Z…

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

c语言link

下面2个编译命令有何不同&#xff1a; 1. gcc a.o -L./ -lmm -o run 2. gcc -L./ -lmm a.o -o run a.o 依赖于libmm.a, 也就是说a.o 调用了libmm.a中的函数&#xff0c;今天在编码时&#xff0c;发现用第2种方式&#xff0c;链接时会报错&#xff0c;提示符号A没有定义, 但…

鸿蒙OS4.0开发学习路线以及注意点

学习鸿蒙OS 4.0开发需要掌握一系列的技能和知识&#xff0c;以下是一个较为详细的学习路线以及注意点&#xff1a; 基础知识准备 了解鸿蒙OS&#xff1a;首先需要对鸿蒙OS有一个基本的了解&#xff0c;包括其特点、架构和应用领域。 掌握Java和C编程语言&#xff1a;这两种编…

layui监听复选框checkbox选中,分页选中处理

具体实现代码如下&#xff08;需要关注三个位置&#xff1a;done表格加载完毕方法&#xff0c;cols中复选框栏定义&#xff0c;table.on中对复选框选中或取消的状态定义&#xff09;这三个地方的代码直接复制去用就行了 最终选中的数据id为&#xff1a;ids <script>layu…

为实体服务器配置Ubuntu

简介 我们在使用虚拟机时&#xff0c;直接在网上找到镜像然后下载到本地&#xff0c;在VMware创建实例时将该iso文件作为镜像源然后进行基础配置就可以轻松安装配置好Linux虚拟机。 在为实体服务器安装Linux系统&#xff0c;同样的&#xff0c;我们也需要镜像源&#xff08;即…

Jmeter多种定时器实现方法解析

1、固定定时器&#xff08;Constant Timer&#xff09; 用法(场景)&#xff1a;更真实的模拟用户场景&#xff0c;需要设置等待时间&#xff0c;或是等待上一个请求的时间才执行&#xff0c;给 sampler 之间的思考时间 备注&#xff1a;如果需要每个步骤均延迟&#xff0c;则…

Python实现AR协方差结构线性回归模型(GLSAR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 GLSAR是具有AR协方差结构的广义最小二乘法线性回归模型。 本项目通过GLSAR回归算法来构建AR协方差结构…

搭建 ElasticSearch 集群环境

安装基础环境 我们用虚拟机创建出3台机器&#xff0c;修改主机名为s1&#xff0c;s2和s3 # 打开如下文件&#xff0c;修改主机名 vim /etc/hostname # 重启机器 reboot查看centos版本为7.9 [roots1 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (AltArch)下载…

常用的Makefile

文章目录 Makefile 单文件编译Makefile 多.c, .h文件编译 Makefile 单文件编译 # 定义变量 CC gcc CFLAGS -Wall -g# 默认目标 all: main# main 目标的依赖为 main.c main: main.o$(CC) $(CFLAGS) -o main main.o# 根据 main.c 文件生成 main.o 目标 main.o: main.c$(CC) $(…

【C语言】记录一次自己犯下的低级错误 o(╯□╰)o(局部数组与指针数组的传参、赋值)

在这里分享一下本人犯下的低级错误&#xff0c;希望大家别掉同样的坑 o(╥﹏╥)o 文章目录 事情原委错误分析及解救办法错误一&#xff1a; 使用局部数组arr并将其作为返回值解决方法&#xff1a;使用动态内存分配来创建数组&#xff0c;并在函数结束前手动释放内存。 错误二&…

在nodejs中使用讯飞星火大模型3.0的demo

需求&#xff1a; 在nodejs引入讯飞星火大模型的api接口, 思路 看了一下官方文档 api连接为一个WebSocket Secure&#xff08;WSS&#xff09;连接&#xff0c;具体思路如下&#xff1a; 引入 crypto 和 ws 模块&#xff0c;分别用于生成加密签名和创建 WebSocket 连接。&am…

力扣每日一题day37[113.路径总和ii]

给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum 22 输出&a…

Maven高级篇

Maven依赖管理原则; 可选依赖&#xff1a;隐藏当前项目中的指定的包&#xff0c;如此&#xff0c;别的包引用当前包时&#xff0c;当前包中的指定包就被隐藏了&#xff0c;在别的包中无法看到隐藏的包 排除依赖&#xff1a;指定排除引用包中所包含的依赖&#xff0c;防止与当…

基于Docker环境下的Jenkins搭建及使用

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

2023年12月21日开发正式版v1.2.3更新·本次更新30多个细节优化·完善丰富后台功能·加入演员关联机制

2023年12月21日开发正式版v1.2.3更新本次更新30多个细节优化完善丰富后台功能加入演员关联机制 产品简介 安卓苹果PCH5四端&#xff0c;蜻蜓z暗影版的衍生级版本&#xff0c;2023年优雅草蜻蜓z冬季雪花限定版&#xff0c;不仅继承了蜻蜓z的精良功能&#xff0c;还特色增加了弹…

Python生成器

一、Python生成器介绍 1.什么是生成器 在Python中&#xff0c;使用了 yield 的函数被称为生成器&#xff08;generator&#xff09;。 跟普通函数不同的是&#xff0c;生成器是一个返回迭代器的函数(一次一个值)&#xff0c;只能用于迭代操作&#xff0c;更简单点理解生成器…

飞天使-k8s知识点8-kubernetes资源对象-编写中

文章目录 资源对象是k8s核心概念 资源对象是k8s核心概念 查看防火墙规则 32002 端口的去向 [rootkubeadm-master1 ~]# iptables -t nat -vnL |grep 32000 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes-dashboard/…

Win系统修改Nginx配置结合内网穿透实现远程访问多个Web站点

文章目录 1. 下载windows版Nginx2. 配置Nginx3. 测试局域网访问4. cpolar内网穿透5. 测试公网访问6. 配置固定二级子域名7. 测试访问公网固定二级子域名 1. 下载windows版Nginx 进入官方网站(http://nginx.org/en/download.html)下载windows版的nginx 下载好后解压进入nginx目…

使用TypeScript范型提升代码复用性和安全性

什么是TypeScript范型 TypeScript 的范型&#xff08;Generics&#xff09;是一种创建可重复使用的组件的方式&#xff0c;这种组件可以对多种数据类型进行操作。 范型本质上是为参数化的类型系统提供了工具&#xff0c;它提供了一种方法&#xff0c;能让你在定义函数、接口或…