安全快速下载¶
模块: mod_secdownload
- 目录
- 安全快速下载
描述¶
已废弃 mod_secdownload 应替换为 lua mod_secdownload
有多种方式处理安全下载机制
- 使用Web服务器和内部HTTP认证
- 使用应用程序进行认证并发送文件
通过应用程序
这两种方式都有局限性
Web服务器- + 快速下载
- + 无额外系统负载
- -- 认证处理不灵活
应用程序
- + 集成到整体布局中
- + 非常灵活的权限管理
- -- 下载会占用应用程序线程/进程
结合这两种方式的一个简单方法可以是
1. 应用认证用户并检查权限以
下载文件。
2. 应用将用户重定向到Web服务器可访问的文件
进行后续下载。
3. Web服务器将文件传输给用户。
由于Web服务器对应用程序中使用的权限一无所知
,因此生成的URL将对所有
知道该URL的用户可用。
mod_secdownload通过引入一种方式来解决此问题,即
在指定时间内验证URL。应用程序必须
生成一个令牌和时间戳,Web服务器在允许
文件由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 '请求超时'。
如果令牌和超时有效,<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