如果让 Nginx 源站做 secure_link 鉴权,CDN 就无法有效缓存(因为每个用户的鉴权参数都不同,Cache Hit 为 0);如果让 CDN 忽略参数强制缓存,防盗链就形同虚设,黑客可以直接绕过签名白嫖你的静态资源。

经过不断调优,最终梳理出了一套解决方案:CDN 边缘鉴权 + 源站暗号保护 + Nginx/API 双轨制路由

核心架构思想

这套架构的核心在于:把防盗链的计算压力推给 CDN 边缘节点,让源站 Nginx 只负责给“自己人”供货。

我们将流媒体下发拆分为两条独立的安全轨道:

  • 【轨道 A - 面向公网】:用户 $\rightarrow$ CDN 节点 (Type A 鉴权) $\rightarrow$ 校验通过 $\rightarrow$ 携带专属暗号回源 $\rightarrow$ Nginx 放行。
  • 【轨道 B - 面向直连】:管理员/内部服务 $\rightarrow$ Nginx 源站 (Secure Link 鉴权) $\rightarrow$ 校验 MD5 签名 $\rightarrow$ 放行。

密钥规划(三把锁)

为了保证绝对安全,系统需要三套完全隔离的密钥/暗号:

  1. **API 密钥 (API_SECRET_KEY)**:保护后端接口不被滥用。
  2. **CDN 鉴权密钥 (CDN_SECRET_KEY)**:用于 URL 签名计算,建议使用 32 位随机字符(如 8kP3mN9vL2qZ5wY1nC7yT0bH6fD4gE2x)。
  3. **源站回源暗号 (ORIGIN_AUTH_HEADER)**:CDN 向源站要文件时的接头暗号(如 SuperSecretOriginKey998877)。

CDN 控制台配置

在云厂商(腾讯云/阿里云等)的 CDN 控制台完成以下三步:

  1. 开启 URL 鉴权 (高级鉴权)
    • 类型选择:类型 A (Type A)
    • 主 KEY:填入你的 CDN_SECRET_KEY
    • 有效时间:10800 秒(3 小时)
  2. 配置回源 HTTP 请求头
    • 增加头部:X-CDN-Auth
    • 头部内容:填入你的 ORIGIN_AUTH_HEADER
  3. 配置缓存规则(精髓所在)
    • /secure_media/ 目录设置为 全部忽略参数
    • 注:正因为 CDN 节点已经做了鉴权,这里忽略参数后,所有合法请求都会共享同一份 FLAC 缓存,源站带宽消耗降为 0!

Nginx 源站双轨配置

改造 Nginx 配置,卸下单一的 secure_link 防备,升级为智能双轨裁决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
location /secure_media/ {
# --- 轨道 B:准备 Nginx 原生直连鉴权 ---
secure_link $arg_md5,$arg_expires;
# 填入 CDN_SECRET_KEY
secure_link_md5 "$secure_link_expires$uri 8kP3mN9vL2qZ5wY1nC7yT0bH6fD4gE2x";

set $allow_access 0; # 默认拦截

# 【轨道 A】带有 CDN 专属暗号回源,直接放行
if ($http_x_cdn_auth = "SuperSecretOriginKey998877") {
set $allow_access 1;
}

# 【轨道 B】直连源站且签名合法,放行
if ($secure_link = "1") {
set $allow_access 1;
}

# 既不是 CDN 回源,直连签名又不对,直接 403
if ($allow_access = 0) {
return 403;
}

# 允许断点续传并映射物理目录
slice 1m;
add_header Accept-Ranges bytes;
alias /var/www/music_files/;
}

FastAPI 后端智能路由

最后,让我们的后端接口根据客户端的请求头,智能生成对应轨道的播放直链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import time
import hashlib
import base64
from urllib.parse import quote
from fastapi import Request

async def generate_secure_url(file_path: str, request: Request) -> str:
secret_key = "8kP3mN9vL2qZ5wY1nC7yT0bH6fD4gE2x"
uri_path = f"/secure_media{file_path}"
encoded_uri_path = quote(uri_path, safe='/')
current_host = request.headers.get("host")

# 前端在请求头带上 x-is-cdn: yes 即可触发 CDN 轨道
if request.headers.get("x-is-cdn") == "yes":
# ========== 【轨道 A】CDN Type A 高级鉴权 ==========
base_host = f"cdn-{current_host}"
timestamp = str(int(time.time()))

# 公式: URI-Timestamp-rand-uid-PrivateKey
sstring = f"{uri_path}-{timestamp}-0-0-{secret_key}"
md5hash = hashlib.md5(sstring.encode('utf-8')).hexdigest()
auth_key = f"{timestamp}-0-0-{md5hash}"

return f"https://{base_host}{encoded_uri_path}?auth_key={auth_key}"

else:
# ========== 【轨道 B】Nginx 直连鉴权 ==========
base_host = request.headers.get("x-forwarded-host") or current_host
expires = int(time.time()) + 3 * 3600

# 公式: expires + URI + 空格 + PrivateKey
string_to_hash = f"{expires}{uri_path} {secret_key}"
md5_hash = hashlib.md5(string_to_hash.encode("utf-8")).digest()
secure_hash = base64.urlsafe_b64encode(md5_hash).decode("utf-8").replace("=", "")

return f"https://{base_host}{encoded_uri_path}?md5={secure_hash}&expires={expires}"

总结

这套方案完美解决了动静分离中的安全与性能痛点:

  1. 100% 缓存命中:大文件流量全被 CDN 挡下,源站服务器岁月静好。
  2. 零带宽盗链:鉴权计算在边缘节点完成,非法请求根本打不到源站。
  3. 高度灵活:内网管理工具或 Navidrome 同步可直接绕过 CDN 走直连轨道,提升效率。

实测下来,哪怕几千首无损 FLAC 批量流转并发,服务器依然稳如泰山。