在互联网应用中,静态资源的安全至关重要。如果不加以保护,他人可以轻易地盗链你的静态资源,这不仅会增加你的服务器带宽成本,还可能影响正常用户的访问体验。今天,我们就来聊聊如何利用 Nginx 实现静态资源的防盗链,主要通过 referer 与 token 验证这两种方式。

一、什么是静态资源防盗链

在深入了解具体的配置方法之前,我们得先弄清楚什么是静态资源防盗链。简单来说,静态资源指的是那些不经常变化的文件,像图片、CSS、JavaScript 文件等。而盗链就是指其他网站未经授权,直接引用你网站上的这些静态资源。比如说,A 网站有一张很吸引人的图片,B 网站直接在自己的页面里引用了这张图片的链接,这样用户访问 B 网站时,实际上是从 A 网站的服务器上加载这张图片。这就会导致 A 网站的带宽被大量占用,增加成本。静态资源防盗链就是要防止这种情况的发生,确保只有授权的访问才能获取这些资源。

二、基于 referer 的防盗链配置

原理

Referer 是 HTTP 请求头的一部分,它会记录请求是从哪个页面发起的。我们可以通过检查这个 Referer 字段,来判断请求是否来自合法的来源。如果 Referer 不在我们允许的列表中,就拒绝该请求。

Nginx 配置示例

以下是一个基于 Nginx 的 referer 防盗链配置示例(使用 Nginx 技术栈):

server {
    listen 80;
    server_name example.com;

    # 定义允许的 referer 列表
    valid_referers none blocked example.com *.example.com;

    location /static/ {
        # 检查 referer
        if ($invalid_referer) {
            return 403;  # 如果 referer 不合法,返回 403 禁止访问
        }
        root /var/www/html;  # 静态资源的根目录
    }
}

代码解释

  • valid_referers:这一行定义了允许的 referer 列表。none 表示没有 referer 的请求是允许的,blocked 表示请求头中带有 Referer 字段,但该字段被防火墙或代理服务器过滤掉的请求是允许的,example.com*.example.com 表示来自 example.com 及其子域名的请求是允许的。
  • $invalid_referer:这是一个 Nginx 变量,如果请求的 referer 不在 valid_referers 列表中,该变量的值为 1,否则为 0。
  • return 403:当 $invalid_referer 为 1 时,返回 403 状态码,表示访问被禁止。

优缺点分析

优点

  • 配置简单:只需要在 Nginx 配置文件中添加几行代码,就可以实现基本的防盗链功能。
  • 不需要额外的服务器资源:只依赖于 Nginx 自身的功能,不需要额外的服务器资源来处理。

缺点

  • 容易被绕过:Referer 字段可以被伪造,攻击者可以通过修改请求头来绕过防盗链机制。
  • 灵活性有限:只能根据请求的来源进行判断,无法对请求进行更细粒度的控制。

注意事项

  • 确保 valid_referers 列表的准确性:如果列表中包含了错误的域名,可能会导致合法的请求被拒绝。
  • 考虑没有 referer 的请求:有些情况下,请求可能没有 referer 字段,比如直接在浏览器地址栏中输入资源的 URL。在配置时,需要根据实际情况决定是否允许这类请求。

三、基于 token 的防盗链配置

原理

Token 是一种身份验证的方式。我们可以为每个请求生成一个唯一的 token,并将其包含在请求的 URL 中。服务器在接收到请求时,会验证这个 token 的有效性。如果 token 有效,就允许访问资源;否则,拒绝请求。

实现步骤

1. 生成 token

在服务器端,我们可以使用一些算法(如 HMAC-SHA256)来生成 token。以下是一个使用 Python(Flask 框架)生成 token 的示例:

import hmac
import hashlib
import time

SECRET_KEY = "your_secret_key"

def generate_token(path, expire=3600):
    timestamp = str(int(time.time() + expire))
    message = f"{path}{timestamp}"
    signature = hmac.new(SECRET_KEY.encode(), message.encode(), hashlib.sha256).hexdigest()
    return f"{timestamp}-{signature}"

代码解释

  • SECRET_KEY:这是一个用于生成 token 的密钥,需要妥善保管。
  • generate_token 函数:接受资源路径 path 和过期时间 expire 作为参数。首先,计算当前时间加上过期时间的时间戳 timestamp。然后,将资源路径和时间戳拼接成一个字符串 message。最后,使用 HMAC-SHA256 算法对 message 进行签名,生成 signature。将时间戳和签名用 - 连接起来,就得到了最终的 token。

2. 在 URL 中添加 token

在生成资源的 URL 时,将生成的 token 添加到 URL 中。例如:

path = "/static/image.jpg"
token = generate_token(path)
url = f"http://example.com{path}?token={token}"
print(url)

3. Nginx 验证 token

在 Nginx 中,我们可以使用 Lua 脚本来验证 token。以下是一个使用 OpenResty(基于 Nginx 和 Lua)验证 token 的示例:

server {
    listen 80;
    server_name example.com;

    location /static/ {
        access_by_lua_block {
            local ngx = ngx
            local args = ngx.req.get_uri_args()
            local token = args["token"]
            if not token then
                ngx.status = ngx.HTTP_FORBIDDEN
                ngx.say("Token is missing")
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            local parts = {}
            for part in string.gmatch(token, "[^-]+") do
                table.insert(parts, part)
            end
            local timestamp = tonumber(parts[1])
            local signature = parts[2]

            if not timestamp or not signature then
                ngx.status = ngx.HTTP_FORBIDDEN
                ngx.say("Invalid token format")
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            if timestamp < ngx.time() then
                ngx.status = ngx.HTTP_FORBIDDEN
                ngx.say("Token has expired")
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end

            local path = ngx.var.uri
            local message = path .. tostring(timestamp)
            local secret_key = "your_secret_key"
            local calculated_signature = ngx.hmac_sha256(secret_key, message)
            if calculated_signature ~= signature then
                ngx.status = ngx.HTTP_FORBIDDEN
                ngx.say("Invalid token signature")
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
        }
        root /var/www/html;
    }
}

代码解释

  • access_by_lua_block:这是 OpenResty 的一个指令,用于在处理请求时执行 Lua 脚本。
  • 首先,从请求的 URL 中获取 token。如果没有 token,返回 403 错误。
  • 然后,将 token 拆分为时间戳和签名两部分。
  • 检查时间戳是否过期,如果过期,返回 403 错误。
  • 最后,重新计算签名,并与请求中的签名进行比较。如果不匹配,返回 403 错误。

优缺点分析

优点

  • 安全性高:Token 难以伪造,因为它是使用密钥和特定算法生成的。
  • 灵活性强:可以对每个请求进行细粒度的控制,比如设置不同的过期时间。

缺点

  • 实现复杂:需要在服务器端和客户端都进行相应的开发,增加了开发成本。
  • 性能开销:验证 token 需要一定的计算资源,可能会对服务器性能产生一定的影响。

注意事项

  • 保管好密钥:密钥是生成和验证 token 的关键,必须妥善保管,防止泄露。
  • 合理设置过期时间:过期时间设置得太短,可能会导致用户在操作过程中频繁需要重新获取 token;设置得太长,会增加 token 被滥用的风险。

四、结合 referer 和 token 的防盗链配置

为了提高防盗链的安全性,我们可以将 referer 和 token 验证结合起来。以下是一个示例配置:

server {
    listen 80;
    server_name example.com;

    # 定义允许的 referer 列表
    valid_referers none blocked example.com *.example.com;

    location /static/ {
        # 检查 referer
        if ($invalid_referer) {
            return 403;
        }

        access_by_lua_block {
            -- 验证 token 的 Lua 脚本,同上面的示例
        }
        root /var/www/html;
    }
}

优点

  • 双重保障:结合了 referer 和 token 的优点,既可以根据请求来源进行初步筛选,又可以通过 token 进行更严格的身份验证,大大提高了防盗链的安全性。

注意事项

  • 配置复杂度增加:需要同时配置 referer 和 token 验证,增加了配置的复杂度。在配置过程中,需要确保两个验证机制的逻辑正确,避免出现冲突。

五、应用场景

图片分享网站

对于图片分享网站来说,图片是核心资源。如果不进行防盗链,其他网站可能会大量盗链这些图片,导致网站的带宽成本大幅增加。通过 Nginx 的防盗链配置,可以有效防止这种情况的发生,保护网站的资源和利益。

视频网站

视频网站的带宽消耗非常大,盗链视频会给网站带来巨大的成本压力。使用 referer 和 token 验证的防盗链机制,可以确保只有合法的用户和网站才能访问视频资源,减少不必要的带宽浪费。

软件下载网站

软件下载网站的静态资源主要是各种软件安装包。盗链这些安装包会影响网站的下载统计和收入。通过防盗链配置,可以控制只有授权的用户才能下载软件,提高网站的安全性和收益。

六、文章总结

通过本文的介绍,我们了解了如何使用 Nginx 实现基于 referer 和 token 的静态资源防盗链配置。Referer 验证简单易行,但容易被绕过;Token 验证安全性高,但实现复杂。在实际应用中,我们可以根据具体的需求和场景,选择合适的防盗链方式,或者将两者结合起来使用,以达到最佳的防盗链效果。同时,在配置过程中,要注意各种细节和注意事项,确保防盗链机制的有效性和稳定性。