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×tamp=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}×tamp={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...×tamp=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_module 和 ngx_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