FastDFS 防盗链详解:Token验证+Nginx白名单保姆级配置指南

FastDFS 防盗链详解:Token验证+Nginx白名单保姆级配置指南

做文件分享服务最头疼的事是什么?链接被人到处转发,流量噌噌噌就没了。我就踩过这坑。去年上线一个文档下载站,用的 FastDFS 存储,结果被人爬虫抓了一晚上,带宽直接跑满。查了一圈日志才发现,光靠默认配置,别人拿到文件 URL 就能直接下载,根本没有任何保护。后来花了点时间,把 FastDFS 自带的防盗链机制配了起来,这才算堵上。来说说怎么配。---## 一、防盗链能防什么说白了就两件事:1. 不想让人蹭链接 ------ 文件 URL 只能你自己生成,别人拿了也没用2. 不想让人爬虫抓 ------ 同一个 IP 反复请求,直接拉黑FastDFS 原生支持这两样,不需要装额外的插件。---## 二、Token 验证原理这是 FastDFS 自带的防盗链核心,原理很简单:token = MD5(filename + timestamp + secret_key)用户请求下载时,URL 长这样:http://192.168.1.10:80/group1/M00/00/01/xxxx.jpg?token=xxxx&timestamp=1745312400服务端收到请求后,用同样的算法重新算一遍 token:- 算出来和 URL 里的一样 ✅ 放行- 算出来不一样,或者 timestamp 已经过期 ❌ 403关键点:secret_key 只有服务端知道 ,客户端只知道最终算出来的 token 值,算不出来原始密钥。---## 三、服务端配置### 3.1 修改 storage.conf登录到 Storage 服务器,修改配置文件:bashvi /etc/fdfs/storage.conf找到并修改以下三项:conf# 开启防盗链http.anti_steal_token = true# 设置加密密钥,自己生成一个随机字符串,越复杂越好http.secret_key = FastDFS$2024#SecretKey# HTTP 端口,通常走 Nginx 代理,所以这里不用改http.tracker_http_port = 80改完重启 Storage:bashsystemctl restart fdfs_storaged### 3.2 Nginx 配置Storage 实际是通过 Nginx 对外提供 HTTP 服务的,所以还要在 Nginx 这一层放行 Token 参数:nginxserver { listen 80; server_name _; # /group1/M00/ 是 FastDFS 的存储路径 location ~/group1/M00/ { # 有 anti_steal_token 时必须设置,否则 Token 参数会被 Nginx 忽略 set $parse_http_token on; # 启用 ngx_fastdfs_module(编译安装时加入此模块) ngx_fastdfs_module; }}> ⚠️ 注意 :Nginx 这一步依赖 ngx_fastdfs_module 模块。如果你用的是官方 nginx_fastdfs 编译版本,已经包含了这个模块;如果是从官方仓库安装的 Nginx,需要重新编译加入这个模块。如果不想编译模块 ,还有一个更简单的方案 ------ 用 Lua 脚本做 Token 校验,Nginx 官方版本装上 ngx_http_lua_module 就能用:nginxlocation ~/group1/M00/ { access_by_lua_file /usr/local/nginx/conf/lua/token_check.lua; proxy_pass http://storage_group;}Lua 脚本 token_check.lua 如下:lualocal secret_key = "FastDFS$2024#SecretKey"local token = ngx.var.arg_tokenlocal timestamp = ngx.var.arg_timestamplocal filename = ngx.var.uriif not token or not timestamp then ngx.exit(ngx.HTTP_FORBIDDEN)end-- 检查是否过期local now = os.time()if now > tonumber(timestamp) then ngx.exit(ngx.HTTP_FORBIDDEN)end-- 计算期望的 tokenlocal input = filename .. timestamp .. secret_keylocal expect = ngx.md5(input)if token ~= expect then ngx.exit(ngx.HTTP_FORBIDDEN)end---## 四、Python 生成下载链接服务端配好了,接下来要生成带 Token 的下载链接供用户使用。下面是 Python 实现,拿去直接能用:python#!/usr/bin/env python3"""FastDFS HTTP Token 生成工具Usage: 生成链接: python3 fastdfs_token.py gen -f /group1/M00/00/01/xxxx.jpg -s your_secret_key 指定过期时间(秒): python3 fastdfs_token.py gen -f /group1/M00/00/01/xxxx.jpg -s your_secret_key -e 3600 校验链接: python3 fastdfs_token.py verify -f /group1/M00/00/01/xxxx.jpg -s your_secret_key -t TOKEN -ts TS"""import hashlibimport timeimport argparseimport sysfrom urllib.parse import quotedef generate_token(filename: str, secret_key: str, timestamp: int = None) -> tuple[str, int]: """ 生成 FastDFS 下载 token Args: filename: FastDFS 文件路径,如 /group1/M00/00/01/xxxx.jpg secret_key: storage.conf 中 http.secret_key 的值 timestamp: Unix 时间戳,默认当前时间 + 300 秒(5分钟) Returns: (token, timestamp) 元组 """ if timestamp is None: timestamp = int(time.time()) + 300 # 拼接原始字符串,注意不是 URL 编码,就是直接拼接 raw = f"{filename}{timestamp}{secret_key}" token = hashlib.md5(raw.encode('utf-8')).hexdigest() return token, timestampdef build_download_url(base_url: str, filename: str, secret_key: str, expire_seconds: int = 300) -> str: """ 构造完整的带 Token 的下载 URL Args: base_url: Storage HTTP 地址,如 http://192.168.1.10:80 filename: FastDFS 文件路径 secret_key: http.secret_key 配置值 expire_seconds: 链接有效期,默认 300 秒 Returns: 完整下载 URL """ ts = int(time.time()) + expire_seconds token, ts = generate_token(filename, secret_key, ts) # URL 编码文件名(如果路径中有中文) safe_filename = quote(filename, safe='/') return f"{base_url.rstrip('/')}/{safe_filename}?token={token}&timestamp={ts}"def verify_token(filename: str, secret_key: str, token: str, timestamp: int) -> bool: """ 本地校验 token 是否合法 Args: filename: FastDFS 文件路径 secret_key: http.secret_key 配置值 token: URL 中的 token timestamp: URL 中的 timestamp Returns: True = 合法, False = 无效或已过期 """ # 先检查是否过期 if int(time.time()) > int(timestamp): return False expected, _ = generate_token(filename, secret_key, int(timestamp)) return token == expected# ─────────────────── CLI ─────────────────── #if __name__ == '__main__': parser = argparse.ArgumentParser(description="FastDFS Token 生成与校验工具") sub = parser.add_subparsers() gen = sub.add_parser('gen', help='生成带 Token 的下载链接') gen.add_argument('-f', '--file', required=True, help='FastDFS 文件路径') gen.add_argument('-s', '--secret', required=True, help='http.secret_key') gen.add_argument('-b', '--base', default='http://127.0.0.1:80', help='HTTP 基础地址') gen.add_argument('-e', '--expire', default=300, type=int, help='过期秒数(默认300)') ver = sub.add_parser('verify', help='校验 token 是否合法') ver.add_argument('-f', '--file', required=True) ver.add_argument('-s', '--secret', required=True) ver.add_argument('-t', '--token', required=True) ver.add_argument('-ts', '--timestamp', required=True) args = parser.parse_args() if len(sys.argv) == 1: parser.print_help() sys.exit(1) if sys.argv[1] == 'gen': url = build_download_url(args.base, args.file, args.secret, args.expire) ts = int(time.time()) + args.expire print(f"📎 完整下载链接:") print(f" {url}") print(f"⏰ 有效期: {args.expire}秒(至 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts))})") elif sys.argv[1] == 'verify': ok = verify_token(args.file, args.secret, args.token, int(args.timestamp)) print("✅ Token 合法" if ok else "❌ Token 无效或已过期")使用示例: bash# 生成下载链接(默认5分钟有效期)$ python3 fastdfs_token.py gen \ -f /group1/M00/00/01/wKgBrGQXy3yAI3C7AABWjRRdqC8072.jpg \ -s FastDFS\$2024#SecretKey \ -b http://192.168.1.10:80 \ -e 3600📎 完整下载链接: http://192.168.1.10:80/group1/M00/00/01/wKgBrGQXy3yAI3C7AABWjRRdqC8072.jpg?token=a1b2c3d4e5f6...&timestamp=1745316000⏰ 有效期: 3600秒(至 2025-04-21 16:20:00)# 校验链接$ python3 fastdfs_token.py verify \ -f /group1/M00/00/01/wKgBrGQXy3yAI3C7AABWjRRdqC8072.jpg \ -s FastDFS\$2024#SecretKey \ -t a1b2c3d4e5f6... \ -ts 1745316000✅ Token 合法---## 五、IP 白名单方案Token 方案只能保护链接不被蹭用,如果想更严------比如只允许特定 IP 访问 ,或者限制单 IP 请求频率 ,可以在 Nginx 这一层做:### 5.1 基础 IP 白名单nginxserver { listen 80; server_name _; # 只允许这些 IP 访问文件 location ~/group1/M00/ { allow 10.0.0.0/8; # 内网段 allow 172.16.0.0/12; # 内网段 allow 192.168.0.0/16; # 内网段 deny all; # 其他全部拒绝 ngx_fastdfs_module; }}### 5.2 请求频率限制(防爬虫)nginxserver { listen 80; server_name _; limit_req_zone $binary_remote_addr zone=download:10m rate=5r/s; location ~/group1/M00/ { # 每个 IP 每秒最多 5 个请求 limit_req zone=download burst=10 nodelay; ngx_fastdfs_module; }}> 💡 小技巧 :结合 ngx_http_limit_req_modulengx_http_limit_conn_module 一起用,一个控频率,一个控并发数。---## 六、生产环境避坑这几个坑我踩过,记录一下:### 坑1:Nginx 没有 ngx_fastdfs_module官方 Nginx 包没有这个模块,需要从 FastDFS 源码重新编译:bash# 1. 下载 FastDFS 源码git clone https://github.com/happyfish100/fastdfs.git# 2. 编译 ngx_fastdfs_module(需要先装 nginx 和 nginx-devel)cd fastdfs/storage/./make.sh ngx_fastdfs# 3. 将生成的模块加入 Nginxnginx -V # 记录现有编译参数./configure --add-module=../nginx_module_path/ngx_fastdfs_module ${原有参数}make && make install嫌编译麻烦,直接用 Lua 脚本方案(前面写过了),不需要这个模块。### 坑2:Token 算出来不一致最容易出错的地方是 filename 要和请求路径一模一样 :- URL 是 /group1/M00/00/01/xxxx.jpg- 代码里算 token 时,filename 也要是 /group1/M00/00/01/xxxx.jpg- 不要加前缀域名 ,不要 urlencode,不要做任何转换,就是原始路径### 坑3:Token 有效期内被爬虫跑满带宽Token 方案防的是"蹭链接",防不了"正经爬虫请求"。如果真的被人爬了,短时间内大量请求:nginx# 触发 5XX 时自动封禁 IP(配合 fail2ban 使用)location = /403 { internal; access_log off; return 403;}配合 Fail2ban + 日志分析,可以做到自动封禁异常 IP。### 坑4:时间不同步Storage 服务器和应用服务器的时间必须同步 ,否则两边算出来的 timestamp 不一致,token 校验直接失败:bash# 配置 NTP 时间同步yum install -y chronysystemctl enable chronydsystemctl start chronydchronyc -a makestep # 立即同步一次---## 七、完整方案推荐实际生产中,建议Token + IP白名单 + 频率限制 三层都配上:用户请求 ↓Token 校验(正确 + 未过期?)──否──→ 403 Forbidden ↓ 是IP 白名单(允许访问?)───────否──→ 403 Forbidden ↓ 是频率限制(不超过阈值?)──────否──→ 429 Too Many Requests ↓ 是↓ 放行,文件下载这个组合能挡住绝大多数蹭流量和爬虫的场景,配置也不算复杂。---有什么问题欢迎留言交流。> 相关链接> - FastDFS 官方 GitHub:https://github.com/happyfish100/fastdfs\> - ngx_fastdfs_module:https://github.com/happyfish100/fastdfs/tree/master/storage/nginx_module

相关推荐
荣--1 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森1 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜2 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB3 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode5 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220705 天前
如何搭建本地yum源(上)
运维
ping某6 天前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
大树888 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠8 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质8 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务