博客 | NGINX

使用 NGINX 和 NGINX Plus 中的安全链接模块保护 URL

NGINX-F5-horiz-black-type-RGB 的一部分
Kunal Pariani 缩略图
库纳尔·帕里亚尼
2016 年 7 月 29 日发布

开源NGINX软件和NGINX Plus作为 Web 服务器、反向代理和内容缓存都非常安全可靠。 为了进一步防止未经授权的客户端访问,您可以使用安全链接模块中的指令,要求客户端在其请求的资产的 URL 中包含特定的散列字符串。

在这篇博文中,我们将讨论如何配置安全链接模块中实现的两种方法。 示例配置片段保护 HTML 和媒体播放列表文件,但可以应用于任何类型的 HTTP URL。 这些方法适用于 NGINX 和 NGINX Plus,但为了简洁起见,在博客的其余部分我们将仅引用 NGINX Plus。

安全链接模块中方法概述

安全链接模块通过将 HTTP 请求的 URL 中的编码字符串与其为该请求计算的字符串进行比较来验证所请求资源的有效性。 如果链接的有效期有限并且时间已过期,则该链接被视为过时。 这些检查的状态被捕获在$secure_link变量中并用于控制处理流程。

如上所述,该模块提供了两种方法。 在给定的httpserverlocation上下文中,只能配置其中一个。

  • 第一个更简单的模式由secure_link_secret指令启用。 编码字符串是根据两个文本字符串的连接计算得出的 MD5 哈希值:URL 的最后一部分和 NGINX Plus 配置中定义的秘密词。 (有关第一个文本字符串的详细信息,请参阅使用基本安全 URL 。)

    要访问受保护的资源,客户端必须在 URL 前缀后立即包含哈希值,该哈希值是没有任何斜杠的任意字符串。 在此示例 URL 中,前缀为videos ,受保护的资源是文件bunny.m3u8

    /视频/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

    此方法的一个用例是,当用户将图像或文档上传到服务器进行共享,但希望阻止任何知道文件名的人访问它,直到发布官方链接。

  • 第二种更灵活的方法由secure_linksecure_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

保护 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 )。

哈希值是根据连接两个元素的文本字符串计算的:

  • URL 中哈希值后面的部分,这里是hls/bunny.m3u8
  • 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 生成哈希值

为了获取客户端必须包含在 URL 中的十六进制格式的 MD5 哈希值,我们运行带有-hex选项的openssl md5命令:

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex (stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

有关以编程方式生成哈希的讨论,请参阅以编程方式生成哈希

服务器对基本安全 URL 的响应

以下示例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

保护 URL 的更灵活的方法是使用secure_linksecure_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 返回403HTTP 响应中的禁止状态代码。

如果哈希值匹配但链接已过期,NGINX Plus 将设置 $安全链接 变量 0;再次 如果 测试失败,但这次 NGINX Plus 返回 410 已消失 HTTP 响应中的状态代码。

在客户端上生成哈希和到期时间

现在让我们看看客户端如何计算包含在 URL 中的md5expires参数。

第一步是确定到期日期的 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”,“谜”);

服务器对带有过期时间的安全 URL 的响应

以下示例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 特定部分的哈希值)来保护文件未授权访问。 添加过期时间还可以限制链接的有效时间,以提高安全性。

要试用 NGINX Plus,请立即开始30 天免费试用联系我们讨论您的用例。


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”