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 首先会判断之前的userid_get 中是否已经正确的获取数据,并进行userid_mark 校验。

if (conf->mark == '\0'
                || (ctx->cookie.len > 23
                    && ctx->cookie.data[22] == conf->mark
                    && ctx->cookie.data[23] == '='))
            {
                return NGX_OK;
            }

            ctx->uid_set[0] = ctx->uid_got[0];
            ctx->uid_set[1] = ctx->uid_got[1];
            ctx->uid_set[2] = ctx->uid_got[2];
            ctx->uid_set[3] = ctx->uid_got[3];

            return NGX_OK;

有数据或者userid_mark校验没问题就会之间返回NGX_OK。ngx_http_userid_set_uid发现 ctx->uid_set[3] 中没数据就会认为不需要设置cookie,会返回NGX_OK结束函数。所以当用户请求的cookie中携带合法的userid字段时,nginx就不会进行set-cookie操作。

如果userid_mark校验没有通过,则会将ctx->uid_got中的数据复制到ctx->uid_set中。此时nginx会在set-cookie中设置正确的userid_mark并返回给用户。

如果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 datetime

class CookieUID(object):
    def __init__(self, cookie_uid):
        self.cookie_uid = cookie_uid
        self.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==')
    cookie_uid.print_info()

输出结果是

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节点。

uid的插入时机

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

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

	static ngx_int_t
	ngx_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的优先级更高。

总结

上面的源码解读通过代码执行的顺序读的,看起来可能有些乱,下面我简单总结一下。

  1. userid模块会进行请求校验:userid的长度是否不小22个字符,前22位是否能被base64解码,是否含userid_mark标记。都通过则不会进行任何操作。

  2. userid的长度是否不小22个字符,是否能被base64解码这两个校验不通过的话nginx error log 会记录 client sent too short userid cookie 或者 client sent invalid userid cookie 。并重新生成userid。userid_mark校验不通过会沿用之前的userid。这些情况下都会重新返回set-cookie。

  3. nginx userid的生成包含nginx server_addr 和 生成时间信息,这些可以通过base64解码打印出来。

  4. nginx userid对header操作默认优先级要高于add_header。如果要修改需要修改ngx_modules.c 并重新编译

结语

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯c/c++程序设计——数位排序

数位排序【第十三届】【省赛】【C组】 题目描述 小蓝对一个数的数位之和很感兴趣&#xff0c;今天他要按照数位之和给数排序。 当两个数各个数位之和不同时&#xff0c;将数位和较小的排在前面&#xff0c;当数位之和相等时&#xff0c;将数值小的排在前面。 例如&#xff0…

CAD objectArx 在操作mfc时出现“不支持尝试执行的操作“

问题原因&#xff1a; ARX中对话框通常继承自CAcUiDialog&#xff0c;CAcUiDialog 构造函数有个参数 HINSTANCE hInstance&#xff0c;默认为 NULL&#xff0c;指定了对话框资源所在DLL进程。如果没有指定该参数&#xff0c;在创建对话框&#xff08;DoModal或Create&#xff…

竞赛保研 基于RSSI的室内wifi定位系统

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; wifi室内定位系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;…

ip addr和ifconfig

ip addr可以显示更多信息&#xff0c;包括为启动的网络驱动如wlan&#xff0c;而ifocnfig只显示在线的驱动。若wlan是down的&#xff0c;则ip addr会显示信息&#xff0c;ifconfig不会显示信息。 ip addr: ifconfig:

网络通信协议

WebSocket通信 WebSocket是一种基于TCP的网络通信协议&#xff0c;提供了浏览器和服务器之间的全双工通信&#xff08;full-duplex&#xff09;能力。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性的连接&#xff…

定制TikTok引流脚本必备功能!

在TikTok的海洋中&#xff0c;如何让你的品牌或产品脱颖而出?除了内容创新&#xff0c;一个高效的TikTok引流脚本也是关键&#xff0c;本文将为你揭示定制TikTok引流脚本必备的四大功能&#xff0c;助你在这场流量大战中占得先机。 一、消息多发 在TikTok上&#xff0c;消息…

本地部署Jellyfin影音服务器并实现远程访问内网影音库

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

鸿蒙的基本入门理解

一、鸿蒙工具的安装&#xff1a; 1、安装&#xff1a;官网 按照官网的步骤&#xff0c;按照好后&#xff0c;可以直接使用previewer预览就可以了【刚入门&#xff0c;不建议大家搞得太多&#xff0c;容易晕】。 如果预览不了&#xff0c;再安装模拟器 2、新建项目&#xff…

企业门户平台全功能解析:从界面到集成,一站式管理与整合

引言 在当今信息时代&#xff0c;企业门户平台作为企业信息化的重要支柱&#xff0c;扮演着连接各项业务、整合数据、提升工作效率的关键角色。它不仅是一个信息集成的平台&#xff0c;更是促进团队协作、提高工作效率的利器。本文将探讨企业门户平台在信息整合和工作效率方面…

故障管理过程

故障管理 故障管理在故障生命周期中的位置 分维度统计分析规律&#xff0c;形成系统化的改进方向跟进每个case的改进方案&#xff0c;彻底消除隐患前事不忘后事之师&#xff0c;供后续参考 故障定级 事故级别服务级别一般事故严重事故重大事故特大事故对外完全停止服务时间一…

大模型工具:LangChain 原理与实战案例

LangChain 是什么&#xff1f; LangChain是一个用于开发由语言模型驱动的应用程序的框架。它使得可以构建以下类型的应用程序&#xff1a; 数据感知&#xff1a;将语言模型与其他数据源连接起来 智能&#xff1a;允许语言模型与其环境进行交互 LangChain的主要价值在于&…

亚信安慧AntDB数据库携手U8C共创未来

AntDB数据库生态负责人在近期举行的商业创新大会上引领着数字化时代的浪潮&#xff0c;推出了令业界瞩目的U8CAntDB联合产品。这一创新性的合作将AntDB数据库与U8C云ERP产品紧密结合&#xff0c;为成长型企业提供了一套全栈、安全可靠的保障&#xff0c;为企业的数智化转型升级…

新/旧版本 QT 下载,全攻略【省资源下载币专用】

看到好多朋友找不到指定版本的QT下载路径&#xff0c;特此更新一篇新/旧版本 QT 下载攻略 收藏一下吧&#xff0c;需要的时候方便查找&#xff0c;能为你省下好多资源下载币。 通过图示可以看出&#xff0c;新旧版本的界限并没有那么明晰&#xff0c;如果你需要的版本两个链接…

docker-compose 安装Sonar并集成gitlab

文章目录 1. 前置条件2. 编写docker-compose-sonar.yml文件3. 集成 gitlab4. Sonar Login with GitLab 1. 前置条件 安装docker-compose 安装docker 创建容器运行的特有网络 创建挂载目录 2. 编写docker-compose-sonar.yml文件 version: "3" services:sonar-postgre…

内网MSF--从入门到熟练

Metasploit就是一个漏洞框架。它的全称叫做The Metasploit Framework&#xff0c;简称叫做MSF。Metasploit 作为全球最受欢迎的工具&#xff0c;不仅仅是因为它的方便性和强大性&#xff0c;更重要的是它的框架。它允许使用者开 发自己的漏洞脚本&#xff0c;从而进行测试。 一…

python实现多层级复选框选中

pythonpyqt5实现多层级复选框选中 效果如何插入一段漂亮的代码片 效果 如何插入一段漂亮的代码片 去博客设置页面&#xff0c;选择一款你喜欢的代码片高亮样式&#xff0c;下面展示同样高亮的 代码片. // An highlighted block class filterWindow(QWidget):def __init__(sel…

路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)

路径规划综述 1. 背景介绍 路径规划是指在给定的环境中找到从起点到终点的最佳路径的过程。它在现实生活中有着广泛的应用&#xff0c;包括无人驾驶、物流配送、机器人导航等领域。随着人工智能和计算机技术的发展&#xff0c;路径规划技术也在不断地得到改进和应用。 路径规划…

【hacker送书第11期】Python数据分析从入门到精通

探索数据世界&#xff0c;揭示未来趋势 《Python数据分析从入门到精通》是你掌握Python数据分析的理想选择。本书深入讲解核心工具如pandas、matplotlib和numpy&#xff0c;助您轻松处理和理解复杂数据。 通过matplotlib、seaborn和创新的pyecharts&#xff0c;本书呈现生动直…

web前端游戏项目-辨色大比拼【附源码】

web前端游戏项目-辨色大比拼【附源码】 《辨色大比拼》是一个旨在测试和提升玩家颜色识别能力的在线游戏。在游戏中&#xff0c;玩家将通过辨识颜色来解谜并推进游戏进程。辨色大比拼也是一个寓教于乐的游戏&#xff0c;它不仅提供了一个有趣的辨色挑战&#xff0c;还能帮助玩…

[C/C++]数据结构: 链式二叉树的构建及遍历

一: &#x1f4ac;二叉树的概念 1.1:&#x1f6a9; 概念 二叉树是指树中节点的度不大于2的有序树,它是一种最简单且重要的树,二叉树的递归定义为:二叉树是一颗空树,或者是一颗由一个根节点和两颗互不相交的,分别称为跟的左孩子和右孩子树组成的非空树,其中左子树和右子树都是二…
最新文章