项目

通用

个人资料

操作

安全快速下载

模块:mod_secdownload

描述

已弃用 mod_secdownload 应该被替换为 lua mod_secdownload

有多种方式来处理安全下载机制

  1. 使用 Web 服务器和内部 HTTP 认证
  2. 使用应用程序进行认证并发送文件
    通过应用程序

这两种方式都有局限性

Web服务器
  • + 快速下载
  • + 无额外系统负载
  • -- 认证处理不灵活

应用程序

  • + 集成到整体布局中
  • + 非常灵活的权限管理
  • -- 下载会占用一个应用程序线程/进程

一种结合这两种方式的简单方法可能是

1. 应用程序验证用户并检查权限以
下载文件。
2. 应用程序将用户重定向到 Web 服务器可访问的文件
以进行后续下载。
3. Web 服务器将文件传输给用户。

由于 Web 服务器不了解
应用程序中使用的权限,因此生成的 URL 将对每个
知道该 URL 的用户都可用。

mod_secdownload 通过引入一种方式消除了此问题,该方式可以
在指定时间内验证 URL。应用程序必须
生成一个令牌和时间戳,Web 服务器在允许文件
下载之前会检查这些信息,然后才允许文件通过
Web 服务器下载。

生成的 URL 必须具有以下格式

<uri-prefix>/<token>/<timestamp-in-hex>/<rel-path>
which looks like "yourserver.com/bf32df9cdb54894b22e09d0ed87326fc/435cc8cc/secure.tar.gz" 

<token> is an MD5 of

1. a secret string (user supplied)
2. <rel-path> (starts with /)
3. <timestamp-in-hex>

如您所见,令牌根本不与用户绑定。唯一
的限制因素是时间戳,它用于
在给定超时 (secdownload.timeout) 后使 URL 失效。

重要

请务必选择一个与示例中使用的密钥
不同的密钥,因为这是令牌中唯一不被
用户知晓的部分。

确保令牌也是十六进制的。根据
您使用的编程语言,可能不需要额外的
步骤。例如,在 PHP 中,MD5 函数会
返回摘要的十六进制值。但是,如果您使用
Java 或 Python 等语言,则需要额外的步骤将
摘要转换为十六进制 (参见下面的 Python 示例)。

如果用户尝试通过选择随机令牌来伪造 URL,
将发送状态 403 'Forbidden'。

如果超时,将发送状态 410 'Gone'。
此在早期版本中曾是 408 'Request Timeout'。

如果令牌和超时有效,则 <rel-path> 将附加到
配置的 (secdownload.document-root) 并传递给
正常的内部文件传输功能。这可能导致
状态 200 或 404。

选项

  secdownload.secret        = <string>
  secdownload.document-root = <string>
  secdownload.uri-prefix    = <string>  (default: /)
  secdownload.timeout       = <short>   (default: 60 seconds)
  secdownload.algorithm     = <string>  ("md5", "hmac-sha1", "hmac-sha256")
  secdownload.path-segments = <number>               (since 1.4.46)
  secdownload.hash-querystr = "enable" | "disable"   (since 1.4.46)

示例

您的应用程序必须生成正确的 URL。

PHP 示例

  <?php

  $secret = "verysecret";
  $uri_prefix = "/dl/";

  # filename
  # please note file name starts with "/" 
  $f = "/secret-file.txt";

  # current timestamp
  $t = time();

  $t_hex = sprintf("%08x", $t);
  $m = md5($secret.$f.$t_hex);

  # generate link
  printf('<a href="%s%s/%s%s">%s</a>',
         $uri_prefix, $m, $t_hex, $f, $f);
  ?>

Ruby On Rails 示例,在辅助函数上下文中使用

  def gen_sec_link(rel_path)
    rel_path.sub!(/^([^\/])/,'/\1')     # Make sure it had a leading slash
    s_secret = 'verysecret'             # Secret string
    uri_prefix = '/dl/'                 # Arbitrary download prefix
    timestamp = "%08x" % Time.now.to_i  # Timestamp, to hex
    token = MD5::md5(s_secret + rel_path + timestamp).to_s    # Token Creation
    '%s%s/%s%s' % [uri_prefix, token, timestamp, rel_path]   # Return the properly formatted string
  end

因此在视图或辅助函数中

<%= link_to "Private Image", gen_sec_link("path/from/download-area/someimage.img") %>

Perl 示例

use strict;
use Digest::MD5 qw< md5_hex >;

my $request = "whatever.txt";
print gen_sec_link($request), "\n";

sub gen_sec_link {
    my $relpath = shift;
    $relpath    =~ s:^([^/]):/$1:;  # make sure it has a leading slash
    my $prefix  = "/static/";       # download prefix
    my $secret  = "verysecret";     # secret string
    my $hextime = sprintf "%08x", time;
    my $token   = md5_hex($secret.$relpath.$hextime);
    return sprintf "%s%s/%s%s", $prefix, $token, $hextime, $relpath
}

Python 示例,可与 Django 或任何其他 Python Web 框架一起使用

  def gen_sec_link(rel_path):
      import time, hashlib
      secret = 'verysecret'
      uri_prefix = '/dl/'
      hextime = "%08x" % time.time()
      token = hashlib.md5(secret + rel_path + hextime).hexdigest()
      return '%s%s/%s%s' % (uri_prefix, token, hextime, rel_path)

注意:当在 Django 中使用非 ASCII 文件名时 (Python 2.x 中的 hashlib 无法正确处理非 ASCII 字符串,会抛出 UnicodeDecodeError 异常,除非提供 UTF-8 编码的字节串而不是默认的 unicode 对象)

  def gen_securelink(rel_path):
      import time, hashlib
      from django.utils.http import urlquote
      rel_path = '/%s/%s' % (self.series.directory, self.filename)
      secret = "flylight" 
      uri_prefix = "http://media.ongoing.ru/download/" 
      hextime = "%08x" % time.time()
      token = hashlib.md5((secret + rel_path + hextime).encode('utf-8')).hexdigest()
      return '%s%s/%s%s' % (uri_prefix, token, hextime, urlquote(rel_path))

(当然,urlquote() 可以被 urllib 方法替换。)

C# 示例

//import library

using System.Security.Cryptography;
using System.Text;

//function :
    public string GetHash(string hashMe) // function get MD5
    {
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        UTF7Encoding encoder = new UTF7Encoding();
        Byte[] encStringBytes;
        encStringBytes = encoder.GetBytes(hashMe);
        encStringBytes = md5.ComputeHash(encStringBytes);
        string strHex = string.Empty;
        foreach (byte b in encStringBytes)       
            strHex += String.Format("{0:x2}", b);        
        return strHex;
    }
    public string GetCurrentEpochTimeInHex() // function get current epoch time in HEX
    {        
        DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0);
        TimeSpan diff = DateTime.UtcNow - EpochTime;
        return ((long)diff.TotalSeconds).ToString("x");
    }
    public string GenerateSecureLink(string rel_path) // ex : rel_path = "/secret-file.txt";
    {
        string t_hex = GetCurrentEpochTimeInHex();
        string serect = "verysecret";
        string uri_prefix = "/dl/";
        string m = GetHash(serect + rel_path + t_hex);
        return String.Format("{0}{1}/{2}{3}", uri_prefix, m, t_hex, rel_path);
    }

C# 中生成 MD5 的参考:http://ok-cool.com/posts/read/125-php-md5-not-the-same-as-net-md5/

JAVA 示例

//Usage
String url = new LighttpdAuthUrl("verysecret","/dl/",videoName).toString();

//Import Packages
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class LighttpdAuthUrl {

    private final String token;
    private final String timestamp;
    private final String authpath;
    private final String filepath;

    public LighttpdAuthUrl(String secret, String authpath, String filepath) {
        this(secret, authpath, filepath, System.currentTimeMillis());
    }

    public LighttpdAuthUrl(String secret, String authpath, String filepath, long time) {
        this.timestamp = Long.toHexString(time/1000L);
        this.authpath = authpath;
        this.filepath = sanitizeFilePath(filepath);
        this.token = generateToken(secret);
    }

    public String sanitizeFilePath(String fpath) {
        if (fpath.charAt(0) != '/') {
            fpath = '/' + fpath;
        }
        return fpath;
    }

    private String generateToken(String secret) {
        return toMD5(secret+filepath+timestamp);
    }

    public static String toMD5(String source) {
        MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        digest.update(source.getBytes());
        byte[] hash = digest.digest();
        return byteArrayToHexString(hash);
    }

    public static String byteToHexString(byte aByte) {
        String hex = Integer.toHexString(0xFF & aByte);
        return ((hex.length() == 1)?"0":"")+hex;
    }

    public static String byteArrayToHexString(byte[] hash) {
        StringBuffer hexString = new StringBuffer();
        for (int i=0;i<hash.length;i++) {
            hexString.append(byteToHexString(hash[i]));
        }
        return hexString.toString();
    }

    public static boolean isBlank(String str) {
        return str == null || str.length() == 0;
    }

    public String getToken() {
        return token;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public String toString() {
        return toUriString();
    }

    public String toUriString() {
        return authpath + token + "/" + timestamp + filepath;
    }
}

Web服务器

服务器必须以相同的方式配置。URI 前缀和
密钥必须匹配::

  server.modules = ( ..., "mod_secdownload", ... )

  secdownload.secret          = "verysecret" 
  secdownload.document-root   = "/home/www/servers/download-area/" 
  secdownload.uri-prefix      = "/dl/" 
  secdownload.timeout         = 10

更新者 gstrauss 近 3 年前 · 46 次修订