直接在 CDN 边缘节点用 Lua 脚本做鉴权,适合自建 OpenResty 架构或支持边缘脚本(如阿里云 EdgeScript、腾讯云 ESA)的公有云环境,能在请求到达源站前拦截非法流量。
先说结论:边缘鉴权能显著降低源站压力,但不同 CDN 厂商的脚本环境差异很大,必须先确认平台支持能力再动手。
- 适合:需要高频校验 Token、IP 黑白名单或防盗链的场景,且 CDN 服务商支持自定义脚本。
- 先准备:确认 CDN 控制台是否开放 Lua 或类 Lua 脚本编辑权限,准备好鉴权算法逻辑(如 HMAC-SHA1)。
- 安全警告:切勿将密钥硬编码在脚本中提交至版本库,建议使用环境变量或配置中心管理密钥。
- 验收:上线前务必在灰度节点测试,确保正常请求不被误拦,异常请求能准确返回 403。
架构原理与优势
传统鉴权往往在源站应用层完成,这意味着所有请求(包括非法的)都会穿透 CDN 打到源站,消耗带宽和 CPU。将鉴权逻辑前置到 CDN 边缘节点,利用 Lua 脚本的高性能和非阻塞特性,可以在网络边缘直接丢弃非法请求。这样做的核心目的是“止血”,防止恶意流量浪费源站资源,同时减少用户等待响应的时间。
自建 OpenResty 完整配置
如果是自建 OpenResty 环境,需确保 Nginx 编译了 lua-nginx-module 模块。以下是一个包含完整 HMAC-SHA1 校验逻辑的可运行示例,实际使用时需替换密钥并建议通过环境变量加载。
1. http 块配置依赖:
http {
lua_package_path "/path/to/your/lib/?.lua;;";
# 建议将密钥存储在环境变量或外部配置,此处仅为示例
lua_shared_dict auth_keys 10m;
server {
listen 80;
server_name example.com;
location / {
access_by_lua_block {
local hmac = ngx.hmac_sha1
local encode_base64 = ngx.encode_base64
-- 生产环境建议从 ngx.var 或 redis 获取密钥,避免硬编码
local secret = "YOUR_SECRET_KEY"
local args = ngx.req.get_uri_args()
local token = args["token"]
local expire = args["expire"]
local uri = ngx.var.uri
-- 1. 基础参数校验
if not token or not expire then
ngx.status = 403
ngx.say("Missing auth parameters")
return ngx.exit(403)
end
-- 2. 过期时间校验 (允许前后 5 分钟误差需自行调整逻辑)
if ngx.time() > tonumber(expire) then
ngx.status = 403
ngx.say("Token expired")
return ngx.exit(403)
end
-- 3. 签名计算与比对
-- 签名源通常包含:过期时间 + 路径 + 随机数
local sign_src = expire .. ":" .. uri
local expected_sign = encode_base64(hmac(secret, sign_src))
if token ~= expected_sign then
ngx.status = 403
ngx.say("Invalid signature")
return ngx.exit(403)
end
}
proxy_pass http://origin_server;
}
}
}2. 密钥安全建议:上述示例中的 secret 仅为演示。生产环境中,建议通过 ngx.var 读取环境变量,或使用 lua-resty-core 结合配置中心动态获取,避免密钥随代码泄露。
公有云边缘脚本适配
公有云 CDN 通常不提供直接写 Nginx Lua 的权限,需在控制台找到“边缘脚本”或“函数计算”功能。不同厂商语法存在差异,以下是阿里云 EdgeScript 的等效逻辑示例,具体 API 请以厂商最新文档为准。
# 阿里云 EdgeScript 示例 (语法与标准 Lua 不同)
$token = $arg_token
$expire = $arg_expire
$uri = $uri
# 参数存在性检查
if eq($token, '') {
exit(403)
}
# 时间过期检查 (边缘脚本有特定时间函数)
if gt($msec, $expire) {
exit(403)
}
# 签名校验需调用厂商提供的加密原语
# $sign = hmac_sha1($secret, $expire . ":" . $uri)
# if ne($token, $sign) { exit(403) }
# 注意:公有云沙箱环境通常不支持 socket、文件 IO 等标准库在腾讯云 ESA 或 AWS CloudFront Functions 中,逻辑类似但语法可能是 JavaScript 或特定 DSL,务必在控制台沙箱中先行验证语法兼容性。
验证与日志排查
配置完成后,需通过以下步骤验证鉴权是否生效且不影响正常业务:
1. 正常请求测试:
curl -i "http://example.com/test.png?expire=1715000000&token=正确的签名值"预期返回 200 OK,且响应头中可能包含边缘节点标识(如 X-Cache 命中状态)。
2. 异常请求测试:
# 测试无 Token
curl -i "http://example.com/test.png"
# 测试过期 Token
curl -i "http://example.com/test.png?expire=1000&token=xxx"
# 测试错误签名
curl -i "http://example.com/test.png?expire=1715000000&token=wrong_sign"预期均直接返回 403 Forbidden,且请求不应出现在源站访问日志中。
3. 查看边缘日志:大多数 CDN 支持实时日志推送。检查日志中是否有脚本执行相关的字段,确认鉴权失败的理由是“签名无效”还是“过期”,以便调整算法。
常见风险与规避
1. 时间同步问题:边缘节点时间与客户端时间不一致会导致签名校验失败。建议服务端生成 Token 时允许一定的时间误差窗口(如前后 5 分钟),脚本校验时不要精确到秒。
2. 缓存污染:如果开启了缓存,务必将鉴权参数(如 token、expire)排除在缓存 Key 之外,否则可能导致一个用户的鉴权结果被缓存给另一个用户。在 Nginx 中可通过 proxy_cache_key 调整,公有云需在缓存配置中忽略特定参数。
3. 语法兼容性:公有云的“边缘脚本”往往是 Lua 的子集或沙箱版本,不支持所有标准库(如 socket、文件 IO)。编写前务必查阅厂商支持的 API 列表,避免使用 require 加载未授权的模块。
4. 性能损耗:虽然 Lua 很快,但复杂的加密运算仍会增加边缘节点延迟。避免在脚本中进行外部 HTTP 请求或数据库查询,所有校验应在本地完成。
5. 回滚方案:在 CDN 控制台保留上一版本的脚本配置,或准备一键关闭脚本功能的开关。一旦脚本出现语法错误导致全站 500,能立即切回直连源站或关闭鉴权。