开源NGINX软件和NGINX Plus作为 Web 服务器、反向代理和内容缓存都非常安全可靠。 为了进一步防止未经授权的客户端访问,您可以使用安全链接模块中的指令,要求客户端在其请求的资产的 URL 中包含特定的散列字符串。
在这篇博文中,我们将讨论如何配置安全链接模块中实现的两种方法。 示例配置片段保护 HTML 和媒体播放列表文件,但可以应用于任何类型的 HTTP URL。 这些方法适用于 NGINX 和 NGINX Plus,但为了简洁起见,在博客的其余部分我们将仅引用 NGINX Plus。
安全链接模块通过将 HTTP 请求的 URL 中的编码字符串与其为该请求计算的字符串进行比较来验证所请求资源的有效性。 如果链接的有效期有限并且时间已过期,则该链接被视为过时。 这些检查的状态被捕获在$secure_link
变量中并用于控制处理流程。
如上所述,该模块提供了两种方法。 在给定的http
、 server
或location
上下文中,只能配置其中一个。
第一个更简单的模式由secure_link_secret
指令启用。 编码字符串是根据两个文本字符串的连接计算得出的 MD5 哈希值:URL 的最后一部分和 NGINX Plus 配置中定义的秘密词。 (有关第一个文本字符串的详细信息,请参阅使用基本安全 URL 。)
要访问受保护的资源,客户端必须在 URL 前缀后立即包含哈希值,该哈希值是没有任何斜杠的任意字符串。 在此示例 URL 中,前缀为videos ,受保护的资源是文件bunny.m3u8 :
/视频/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8
此方法的一个用例是,当用户将图像或文档上传到服务器进行共享,但希望阻止任何知道文件名的人访问它,直到发布官方链接。
第二种更灵活的方法由secure_link
和secure_link_md5
指令启用。 此处编码的字符串是 NGINX Plus 配置文件中定义的变量的 MD5 哈希值。 最常见的是,包含$remote_addr
变量以限制对特定客户端 IP 地址的访问,但您可以使用其他值,例如$http_user_agent
,它可以捕获User-Agent
标头并限制对某些浏览器的访问。
或者,您可以指定一个到期日期,在此日期之后,即使哈希值正确,URL 也将不再起作用。
客户端必须将md5参数附加到请求 URL 来指定哈希值。 如果散列字符串中包含到期日期,则客户端还必须附加expires参数来指定日期,如以下用于请求受保护文件pricelist.html 的示例 URL 所示:
/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740
安全链接模块包含在nginx.org 预构建的开源 NGINX 二进制文件、操作系统供应商提供的 NGINX 包以及NGINX Plus中。 当您从源代码构建 NGINX 时,它默认不包含在内;通过在configure
命令中包含--with-http_secure_link_module
参数来启用它。
保护 URL 安全的更基本方法是使用secure_link_secret
指令。 在下面的示例代码片段中,我们保护了一个名为/bunny.m3u8的 HTTP 实时流 (HLS) 媒体播放列表文件。 它存储在/opt/secure/hls目录中,但使用以videos前缀开头的 URL 向客户端显示。
服务器 {
listen 80;
server_name secure-link-demo;
location /videos {
secure_link_secret enigma;
if ($secure_link = "") { return 403; }
rewrite ^ /secure/$secure_link;
}
location /secure {
internal;
root /opt;
}
}
使用此配置,要访问文件/opt/secure/hls/bunny.m3u8 ,客户端必须提供以下 URL:
/视频/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8
散列字符串紧跟在前缀之后,前缀是一个没有任何斜杠的任意字符串(此处为videos )。
哈希值是根据连接两个元素的文本字符串计算的:
secure_link_secret
指令的参数,这里是enigma
。如果客户端的请求 URL 没有正确的哈希值,NGINX Plus 会将$secure_link
变量设置为空字符串。 这 如果
测试失败,NGINX Plus 返回 403
禁止
HTTP 响应中的状态代码。
否则(意味着哈希是正确的),重写
指令会重写 URL – 在我们的示例中为/secure/hls/bunny.m3u8($secure_link变量
捕获哈希后面的 URL 部分)。 以/secure开头的 URL 由第二个位置
块处理。 该块中的根
指令将/opt设置为请求文件的根目录,而内部
指令指定该块仅用于内部生成的请求。
为了获取客户端必须包含在 URL 中的十六进制格式的 MD5 哈希值,我们运行带有-hex
选项的openssl
md5
命令:
# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex (stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4
有关以编程方式生成哈希的讨论,请参阅以编程方式生成哈希。
以下示例curl
命令显示了服务器如何响应不同的安全 URL。
如果 URL 包含正确的 MD5 哈希,则响应为200
好的
:
# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1 HTTP/1.1 200 OK
如果 MD5 哈希不正确,则响应为403
禁止
:
# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1 HTTP/1.1 403 禁止访问
如果bunny.m3u8的哈希值用于不同的文件,则响应也是如此403
禁止
:
# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1 HTTP/1.1 403 禁止访问
保护 URL 的更灵活的方法是使用secure_link
和secure_link_md5
指令。 在此示例中,我们使用它们仅允许 IP 地址为 192.168.33.14 的客户端访问/var/www/files/pricelist.html文件,并且只能访问到 2016 年 12 月 31 日。
我们的虚拟服务器监听端口 80 并处理位置
/files
块下的所有安全 HTTP 请求,其中root
指令将/var/www设置为请求文件的根目录。
secure_link
指令定义了两个捕获请求 URL 中的参数的变量: $arg_md5
设置为md5参数的值, $arg_expires
设置为expires参数的值。
secure_link_md5
指令定义了经过散列的表达式,以生成请求的 MD5 值;在 URL 处理期间,会将散列与$arg_md5
的值进行比较。 此处的示例表达式包括请求中传递的到期时间(在$secure_link_expires
变量中捕获)、URL( $uri
)、客户端 IP 地址( $remote_addr
)和单词enigma
。
服务器 {
listen 80;
server_name secure-link-demo;
location /files {
root /var/www;
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
}
通过此配置,要访问/var/www/files/pricelist.html ,IP 地址为 192.168.33.14 的客户端必须在2016 年 12 月 31 日星期六 23:59:00 UTC之前发送此请求 URL:
/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740
如果客户端发送的 URL 中的哈希值(在$arg_md5
变量中捕获)与从secure_link_md5
指令计算的哈希值不匹配,NGINX Plus 会将$secure_link
变量设置为空字符串。 if
测试失败,NGINX Plus 返回403
HTTP 响应中的禁止
状态代码。
如果哈希值匹配但链接已过期,NGINX Plus 将设置 $安全链接
变量 0
;再次 如果
测试失败,但这次 NGINX Plus 返回 410
已消失
HTTP 响应中的状态代码。
现在让我们看看客户端如何计算包含在 URL 中的md5和expires参数。
第一步是确定到期日期的 Unix 时间等效值,因为该值以$secure_link_expires
变量的形式包含在散列表达式中。 为了获取 Unix 时间(自纪元(1970-01-01 00:00:00 UTC)以来的秒数),我们使用带有-d
选项和+%s
格式说明符的date
命令。
在我们的示例中,我们将到期时间设置为2016 年 12 月 31 日星期六 23:59:00 UTC ,因此命令为:
#日期 -d "2016-12-31 23:59" +%s1483228740
客户端将此值作为expires=1483228740参数包含在请求 URL 中。
现在我们通过三个命令运行由secure_link_md5
指令 - $secure_link_expires$uri$remote_addr
enigma
- 定义的字符串:
-binary
选项的openssl
md5
命令以二进制格式生成 MD5 哈希。openssl
base64
命令将Base64编码应用于散列值。tr
命令用连字符 ( -
) 替换加号 ( +
),用下划线 ( _
) 替换斜线 ( /
),并从编码值中删除等号 ( =
)。对于我们的示例,完整的命令是:
# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d = AUEnXC7T-Tfv9WLsWbf-mw
客户端将该值作为md5=AUEnXC7T-Tfv9WLsWbf-mw参数包含在请求 URL 中。
如果您的 NGINX Plus Web 服务器正在从应用服务器提供动态内容,则 NGINX Plus 和应用服务器都需要使用相同的安全 URL。 您可以通过编程方式生成 URL 中md5参数的哈希值。 以下 Node.js 函数生成一个与上面的 NGINX Plus 配置片段中定义的哈希匹配的哈希。 它以到期时间、URL、客户端 IP 地址和密码作为参数,并返回 Base64 编码的二进制格式 MD5 哈希。
var crypto = require("crypto");
function generateSecurePathHash(expires, url, client_ip, secret) {
if (!expires || !url || !client_ip || !secret) {
return undefined;
}
var input = expires + url + client_ip + " " + secret;
var binaryHash = crypto.createHash("md5").update(input).digest();
var base64Value = new Buffer(binaryHash).toString('base64');
return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}
为了计算当前示例的哈希值,我们传入以下参数:
生成SecurePathHash(新日期('2016年12月31日 23:59:00')。getTime()),'/files/pricelist.html',“192.168.33.14”,“谜”);
以下示例curl
命令显示了服务器如何响应安全 URL。
如果 IP 地址为 192.168.33.14 的客户端包含正确的 MD5 哈希值和过期时间,则响应为200
好的
:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1 HTTP/1.1 200 OK
如果具有不同 IP 地址的客户端发送相同的 URL,则响应为403
禁止
:
# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1 HTTP/1.1 403 禁止访问
如果md5参数的哈希值不正确,则响应为403
禁止
:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1 HTTP/1.1 403 禁止访问
如果 URL 已过期( expires参数所表示的日期是过去的日期),则响应为410
已消失
:
# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1 HTTP/1.1 410 Gone
这是另一个带有到期日期的安全 URL 的示例,用于保护媒体资产的播放列表和片段文件。
与前面的示例的一个区别是,我们在此处添加了一个map
配置块,以从播放列表( .m3u8文件)和 HLS 段( .ts文件)中删除扩展名,因为我们在$file_name
变量中捕获文件名,该变量传递给secure_link_md5
指令。 这有助于确保对各个.ts段以及播放列表的请求的安全。
与第一个例子的另一个不同之处在于,我们在secure_link_md5
指令中包含了$http_user_agent
变量(捕获User-Agent
标头),以限制对特定 Web 浏览器上的客户端的访问(例如,让 URL 在 Safari 上运行,但不能在 Chrome 或 Firefox 上运行)。
映射 $uri $file_name {
默认 none;
“~*/s/(?<name>.*).m3u8” $name;
“~*/s/(?<name>.*).ts” $name;
}
服务器 {
listen 80;
server_name secure-link-demo;
location /s {
root /opt;
secure_link $arg_md5,$arg_expires;
secure_link_md5 “$secure_link_expires$file_name$http_user_agent enigma”;
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
}
NGINX 中的安全链接模块使您能够通过添加编码数据(如 URL 特定部分的哈希值)来保护文件未授权访问。 添加过期时间还可以限制链接的有效时间,以提高安全性。
“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”