如何基于Nginx实现URI仅限首个请求IP访问(secure_link模块受限场景)
针对你这个小型视频CDN遇到的问题——因为客户端到请求路由器和边缘服务器的remote_address不一致,没法用Nginx的secure_link模块按IP限制,要实现单个URI仅限首个请求它的IP访问,我这边有一套基于Nginx+Redis的落地方案,也给你补充下备选思路:
核心思路
既然没法在生成token时提前绑定客户端IP,那我们就把IP绑定的逻辑放到边缘服务器(Nginx)侧来做动态处理:当第一个请求某个URI的请求到达时,我们把这个URI和请求IP绑定并存储起来;后续所有请求这个URI的请求,只要IP和存储的不一致就直接拒绝。同时还要和原有的expires过期逻辑联动,避免无效记录占用资源。
具体落地方案(Nginx + Redis)
这个方案适合单台或多台边缘服务器的场景,多台的话用Redis做共享存储来同步绑定记录,单台也可以用Redis或者Nginx内存缓存,不过Redis更可靠。
1. 前置准备
首先确保你的Nginx安装了ngx_http_redis_module和ngx_http_set_misc_module,如果用OpenResty的话这些模块都是自带的,会更省心。另外需要部署一个Redis实例(多边缘的话建议用Redis集群)。
2. Nginx配置示例
下面是完整的配置逻辑,我给你加了详细注释:
http { # 配置Redis连接信息 redis_server 127.0.0.1 6379; # 换成你的Redis地址和端口 # redis_password your_redis_pwd; # 如果Redis设了密码就打开这行 server { listen 80; server_name your_edge_domain; # 匹配你的视频URI格式:/videos/{token}/{expires}/{视频路径} location ~ ^/videos/([^/]+)/([^/]+)/(.+)$ { # 从URI里提取关键参数 set $token $1; set $expires $2; set $video_path $3; # 构造唯一的Redis键:用token+expires+视频路径组合,确保每个URI对应唯一的键 set $redis_key "cdn_uri_lock:$token:$expires:$video_path"; # 第一步:从Redis获取这个URI已经绑定的IP redis_get $redis_key; set $bound_ip $redis_reply; # 第二步:如果还没绑定IP,就把当前请求的IP存到Redis,同时设置过期时间和URI的expires同步 if ($bound_ip = "") { # 用Lua脚本计算剩余过期时间,避免Nginx原生指令的局限性 rewrite_by_lua_block { local expires_ts = tonumber(ngx.var.expires) local now = ngx.time() local ttl = expires_ts - now if ttl <= 0 then # URI已经过期,直接返回403 ngx.exit(ngx.HTTP_FORBIDDEN) end ngx.var.ttl = ttl } # 将当前IP存入Redis,过期时间和URI的expires保持一致 redis_setex $redis_key $ttl $remote_address; } # 第三步:如果已经绑定了IP,对比当前请求IP和绑定IP,不一致就拒绝 if ($bound_ip != "" and $bound_ip != $remote_address) { return 403; } # 第四步:正常转发到源服务器或者本地存储 proxy_pass http://your_source_server/$video_path; # 如果视频存在边缘本地,就换成: # root /path/to/your/video/storage; # try_files /$video_path =404; } } }
3. 关键细节说明
- Redis键的唯一性:用
token:$expires:$video_path组合作为键,因为这三个部分完全确定了一个唯一的访问URI,不会和其他URI冲突。 - 过期时间同步:Redis键的过期时间和URI中的
expires时间戳保持一致,这样URI过期后,Redis会自动删除这条绑定记录,不用手动清理。 - Lua脚本的作用:Nginx原生指令没法直接计算时间差,所以用Lua来判断URI是否已经过期,避免给过期的URI做无效的IP绑定。
- 多边缘服务器场景:只要所有边缘服务器连接同一个Redis集群,就能保证URI的绑定IP在所有节点中同步,不会出现不同节点允许不同IP访问的情况。
备选方案(请求路由器侧优化)
如果你能修改请求路由器的代码,也可以调整token生成逻辑:生成URI时附带一个一次性的临时标识,然后在边缘服务器侧,第一个请求用这个标识绑定IP,后续请求需要同时验证标识和IP。不过这个方案需要改动请求路由器的代码,相对复杂一些,适合能完全控制请求路由器开发的场景。
注意事项
- 性能影响:Redis的读写操作非常轻量,对边缘服务器的性能几乎没有影响,但要确保Redis和边缘服务器在同一个机房,避免网络延迟。
- 异常降级:如果Redis宕机,可以考虑临时降级(比如暂时允许所有IP访问,或者直接返回503),避免影响正常业务。
- 安全补充:别忘了保留原有的
token验证逻辑,防止恶意构造大量URI来占用Redis资源。
内容的提问来源于stack exchange,提问作者Nguyễn Tuấn Nghĩa

