融合 CDN

  • 时间戳防盗链

    最近更新时间:2018-08-31 15:15:32

    为保护用户站点的内容资源不被非法站点下载盗用,七牛融合CDN支持时间戳防盗链功能,通过对时间有关的字符串进行签名,将时间、签名信息通过一定的方式传递给 CDN 边缘节点服务器作为判定依据通过鉴权方式来正确响应合法请求、拒绝非法请求。您可以登录七牛开发者平台选择融合CDN控制台进行防盗链功能的配置。

    算法说明

    基于时间戳的防盗链是通过对时间有关的字符串进行签名,将时间、签名通过一定的方式传递给 CDN 服务器作为判定依据,CDN 边缘节点根据约定的算法判断来访URL是否有访问权限。

    通过,执行下一步;不通过,响应 HTTP status code 403。

    若同时配置了 Referer、UA防盗链、时间戳防盗链,有一项不满足条件,即为不通过,响应 403 。

    签名参数

    • T:URL 过期时间。按 unix_time 的 16进制小写形式表示。 如 2015-08-01 00:00:00 –> 1438358400 –> 55bb9b80

    • key:在开启时间戳防盗链时,可以由七牛提供:(开发者中心 -> 融合CDN -> 防盗链设置 ->时间戳防盗链 ->KEY生成器),使用其中一个即可。也可以自行使用算法生成,如下方生成实例中gen_key()函数

    • path:访问资源的 URL 中的路径部分,例如:访问的URL为 http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1,则 path = /DIR1/dir2/vodfile.mp4(注意不含 querystring 部分)

    签名算法

    • 签名原始字符串 S = key + url_encode(path) + T 。斜线 / 不编码。

    • 签名 SIGN = md5(S).to_lower(),to_lower 指将字符串转换为小写;

    注:本文所提到的 url_encode 算法。

    签名参数传递方式

    作为URL查询参数。

    例如原始访问的URL为: http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1

    最终形成的访问URL为: http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1&sign=<SIGN>&t=<T>

    • 签名参数 sign、 t ,sign 在前,t 在后;

    • <SIGN><T> 替换为对应的值, 实际url中不含<> ;

    访问url

    访问 url 的 path 部分也需要 url_encode,其算法与签名时使用 url_encode 算法一致。斜线 / 不编码。

    访问 url 为:

    scheme + "://" + host + url_encode(path) + query_part
    

    http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1&sign=19eb212771e87cc3d478b9f32d6c7bf9&t=55bb9b80

    http://xxx.yyy.com/DIR1/%E4%B8%AD%E6%96%87/vodfile.mp4?v=1.2&sign=6356bca0d2aecf7211003e468861f5ea&t=55bb9b80

    注:

    1. 本文所提到的 url_encode 算法,斜线 / 不编码。
    2. 访问 url 的 path 部分推荐按 url_encode 编码,如下例。

    示例

    例1:

    URL http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1 ,假设 key = 12345678 ;过期时间为 2015-08-01 00:00:00 ,即 1438358400 ,也就是 T = 55bb9b80S = 12345678/DIR1/dir2/vodfile.mp455bb9b80SIGN = 19eb212771e87cc3d478b9f32d6c7bf9 , 访问 url 为:

    http://xxx.yyy.com/DIR1/dir2/vodfile.mp4?v=1.1&sign=19eb212771e87cc3d478b9f32d6c7bf9&t=55bb9b80

    之后将 url 地址填写在上图中的 检查url 处验证。

    例2:

    URL http://xxx.yyy.com/DIR1/中文/vodfile.mp4?v=1.2 ,假设 key = 12345678T = 55bb9b80S = 12345678/DIR1/%E4%B8%AD%E6%96%87/vodfile.mp455bb9b80SIGN = 6356bca0d2aecf7211003e468861f5ea ,访问 url 为:

    http://xxx.yyy.com/DIR1/%E4%B8%AD%E6%96%87/vodfile.mp4?v=1.2&sign=6356bca0d2aecf7211003e468861f5ea&t=55bb9b80

    之后将 url 地址填写在上图中的 检查url 处验证。

    生成实例

    python版

    #! /usr/bin/env python
    #coding:utf-8
    
    from hashlib import md5
    import urllib
    import sys
    import time
    from urlparse import urlparse, parse_qs
    import traceback
    import uuid
    import base64
    
    # 要求正确的 url_encode 编码,斜线 / 不编码;
    # 井号 # 等在浏览器会直接识别为其它含义,若在 path 中必须编码;
    # 问号 ? 等在 url 中有特殊含义,若在 path 中必须编码;
    # 部分字符 "~!$&'()*+,:;=@[]" 不含双引号 ",虽有特殊含义,但在 path 部分,编码与否,都可以正常访问;
    # 另一些,如 双引号 ", 空格 " ", 汉字等必须编码;
    #
    # 建议 url 的 path 中尽量不含上述部分,建议 url 中尽量不含上述部分。
    #
    # 参考
    # https://www.wikiwand.com/zh-cn/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81
    # https://www.wikiwand.com/en/Percent-encoding
    # https://www.wikiwand.com/de/URL-Encoding
    def url_encode(s):
        return urllib.quote(s.decode(sys.stdin.encoding).encode("utf8"), safe="/")
    
    def to_deadline(rang):
        return int(time.time()) + rang
    
    def t16(t):
        return hex(t)[2:].lower()   # 16 进制小写形式
    
    def summd5(str):
        m = md5()
        m.update(str)
        return m.hexdigest()
    
    def sign(key, t, path):
        a = key + url_encode(path) + t
        print("S: " + a)
        sign_s = summd5(a).lower()
        sign_part = "sign=" + sign_s + "&t=" + t
        return sign_part
    
    def sign_url(key, t, p_url):
        url = urllib.unquote(p_url)
        up = urlparse(url)
        path = up.path
        sign_part = sign(key, t, path)
        p_query = up.query
        if p_query:
            query_part = "?" + p_query + "&"+ sign_part
        else:
            query_part = "?" + sign_part
    
        return up.scheme + "://" + up.netloc + url_encode(path) + query_part
    
    def printurl_encode_help():
        print '''
    # 要求正确的 url_encode 编码,斜线 / 不编码;
    # 井号 # 等在浏览器会直接识别为其它含义,若在 path 中必须编码;
    # 问号 ? 等在 url 中有特殊含义,若在 path 中必须编码;
    # 部分字符 "~!$&'()*+,:;=@[]" 不含双引号 ",虽有特殊含义,但在 path 部分,编码与否,都可以正常访问;
    # 另一些,如 双引号 ", 空格 " ", 汉字等必须编码;
    #
    # 建议 url 的 path 中尽量不含上述部分,建议 url 中尽量不含上述部分。
    #
    # 参考
    # https://www.wikiwand.com/zh-cn/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81
    # https://www.wikiwand.com/en/Percent-encoding
    # https://www.wikiwand.com/de/URL-Encoding
        '''
    
    def signt_help():
        print
        print "./signt.py time <key> <url> <t,eg: 3600>"
        print "./signt.py deadline <key> <url> <deadline>"
        print "./signt.py check <key> <signed_url>"
        print "./signt.py show <t, eg: 55bb9b80>"
        print "./signt.py genkey"
        print "\n"
        print '''
    # example:
    
    # url = "http://xxx.yyy.com/DIR1/中文/vodfile.mp4?sfdf=dfe"
    # key = 12345678
    # 过期时间点: Sat Aug  1 00:00:00 2015  ==> 1438358400
    
    # 执行: signt.py deadline 12345678  http://xxx.yyy.com/DIR1/中文/vodfile.mp4?sfdf=dfe 1438358400
    # 签名 url 为: http://xxx.yyy.com/DIR1/%E4%B8%AD%E6%96%87/vodfile.mp4?sfdf=dfe&sign=6356bca0d2aecf7211003e468861f5ea&t=55bb9b80
    
    # 执行: signt.py check 12345678 "http://xxx.yyy.com/DIR1/%E4%B8%AD%E6%96%87/vodfile.mp4?sfdf=dfe&sign=6356bca0d2aecf7211003e468861f5ea&t=55bb9b80"
    # 显示: True
        '''
        print "\n"
        printurl_encode_help()
    
    def sign_time(key, url, rang):
        print("range: " + str(rang))
        deadline = to_deadline(rang)
        sign_deadline(key, url, deadline)
    
    def sign_deadline(key, url, deadline):
        print("\nkey: " + key)
        print("\nurl: " + url)
        print("\ndeadline: " + t16(deadline) + ", " + str(deadline) + ", " + time.ctime(deadline))
        print
        t = t16(deadline)
        signed_url = sign_url(key, t, url)
        print "\nsigned_url:"
        print signed_url
        print
    
    # signed_url 是正确 url_encode 编码后签出的 url
    # 见 url_encode 方法注释
    def sign_check(key, signed_url):
        print "\n 要求: 待检测的 url 是正确 url_encode 编码后签出的 url"
        printurl_encode_help()
        u = urlparse(signed_url)
        t = parse_qs(u.query)["t"][0]
        sign_s = summd5(key + u.path + t).lower()
        print
        print("deadline: " + str(int(t, 16)) + " , " + time.ctime(int(t, 16)))
        print(sign_s)
        print(parse_qs(u.query)["sign"][0] == sign_s)
    
    def show_t(t):
        i_t = int(t, 16)
        s_t = time.ctime(i_t)
        print(t + " : " + str(i_t) + " : " + s_t)
    
    # 仅用于测试
    def gen_key():
        print base64.urlsafe_b64encode(str(uuid.uuid4()))[:40].lower()
    
    try:
        type = sys.argv[1]
    
        if type == "time":
            sign_time(sys.argv[2], sys.argv[3], int(sys.argv[4]))
        elif type == "deadline":
            sign_deadline(sys.argv[2], sys.argv[3], int(sys.argv[4]))
        elif type == "check":
            sign_check(sys.argv[2], sys.argv[3])
        elif type == "show":
            show_t(sys.argv[2])
        elif type == "genkey":
            gen_key()
        else:
            signt_help()
    except Exception as e:
        print traceback.format_exc()
        signt_help()
    

    服务端验证

    服务端拿到原始的 url ,直接解析出 host, path, sign, t ,再签名。

    算法: S = key + path + t,SIGN = md5(S).to_lower()

    注意此处没有 url_encode 操作。

    原始的 url 指未经 url_decoded 的内容。

    以 nginx 为例说明:

    浏览器发出实际请求url: http://example.com/foobar/hello%20world

    nginx变量 $uri: http://example.com/foobar/hello world

    nginx变量 $request_uri: http://example.com/foobar/hello%20world

    原始的 url 内容和 $request_uri 内容一致。

    要求验证签名时使用 $request_uri ,此值为原始值,内容是正确的经过 url encode 的内容,所以 path 不用编码。

    服务端不能使用 $uri 获取各参数,然后再调用 url_encode 来获取待签名的 path。path url_encode 后再 url_decode,获得的内容与原 path 可能不一样。

    http://example.com/foobar/hello+world

    http://example.com/foobar/hello%2Bworld

    http://example.com/foobar/hello%2bworld

    以上三个url都是合法的访问同一资源的链接。相同 key 、T,执行签名后会有三个不同的值。 %2b url_decode 再 url_encode 可能得到 %2B,导致签名不一致。

    操作说明

    登录七牛开发者平台后,进入融合CDN控制台,选择【域名管理】,点击【配置】:

    域名列表

    进入域名配置界面,访问控制模块下的时间戳防盗链点击【修改配置】进行时间戳防盗链的配置。

    时间戳防盗链

    时间戳防盗链:默认为关闭,开启时,会同时生成两组可用的key,用户需先按照文档说明在代码中将Key配置进您的URL,再进行格式检查,然后确认开启;还可以自定义输入KEY,支持输入备用KEY且不能与主KEY相同;同时需要输入检查URL,以保证鉴权服务正常可用,以免影响服务。

    时间戳防盗链

    注:时间戳防盗链与回源鉴权功能不能同时开启。

    以上内容是否对您有帮助?
  • Icon free helper
    Close