OSS最佳实践:防盗链

  • A+
所属分类:对象存储OSS
高性能企业级服务器首台5折

背景

A是某一网站站长,A网站中的图片和音频视频链接等静态资源都保存在阿里云对象存储OSS上。以图片为例,A在OSS上存放的URL为

1
http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png

OSS资源外链地址见OSS 地址,这样的URL(不带签名)要求用户的Bucket权限为公开读权限。

B是另一网站的站长,B在未经A允许的情况下使用A网站的图片资源,放置在自己网站的网页中,通过这种方法盗取空间和流量。这样的情况下,第三方网站用户看到的是B网站,但并不清楚网站里的图片来源。由于OSS是按使用量来收费,这样用户A在没有获取任何收益的情况下,反而承担了资源使用费用。

本文适用于在网页中使用了OSS资源作为外链的用户,并介绍类似A用户将资源存放在OSS上后,如何通过设置防盗链的方法避免承担不必要的资源使用费用。

推送一则优惠信息:开启云存储钜惠新时代,阿里云OSS/3年存储包冰点特价,新老用户同享,点击下面按钮:

实现方法

目前OSS提供的防盗链方法主要有以下两种:

  • 设置Referer。该操作通过控制台和SDK均可进行,用户可根据自身需求进行选择。
  • 签名URL,适合习惯开发的用户。

本文将提供如下两个示例:

  • 通过控制台设置Referer防盗链
  • 基于PHP SDK动态生成签名URL防盗链

设置Referer

该部分主要说明什么是Referer,以及OSS如何利用Referer做防盗链。

  • Referer是什么

    Referer是HTTP Header的一部分,当浏览器向网站Web服务器发送请求的时候,通常会带上Referer,告诉服务器此次请求的链接来源。从以上示例来看,假如用户B的网站为userdomain-steal,想盗链用户A的图片链接

    1
    http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png

    。A的网站域名

    1
    userdomain

    假设盗链网站userdomain-steal的网页如下:

    
    
    1
    2
    3
    4
    <span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a test<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>

    假设源站为userdomain的网页如下:

    
    
    1
    2
    3
    4
    <span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is my test link from OSS URL<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
    • 当互联网用户用浏览器访问B的网站页面
      1
      http://userdomain-steal/index.html

      ,网页里链接是A的网站的图片。由于从一个域名(userdomain-steal)请求跳到了另一个域名(oss-cn-hangzhou.aliyuncs.com),浏览器就会在HTTP请求的Header中带上Referer,如图所示:

      可以看到浏览器在HTTP请求中的Referer为

      1
      http://userdomain-steal/index.html

      。本文主要是使用chrome的开发者模式来查看网页请求的,如图:

    • 同样浏览器访问
      1
      http://userdomain/error.html

      ,也可以看到浏览器的Referer为

      1
      http://userdomain/error.html


    • 如果浏览器直接输入地址,可以看到,请求中的Referer为空。

      如果A没有在OSS进行任何Referer相关设置,以上三种情况都是可以访问用户A的图片链接。

  • OSS通过Referer防盗链的原理

    由此可见,浏览器在请求OSS资源时,如果发生页面跳转,浏览器会在请求中带入Referer,此时Referer的值为上一页面的URL,有的时候Referer也会为空。

    针对这两种情况,OSS的Referer功能提供两种选择:

    • 设置是否允许空Referer访问。不能单独设置,需要配合Referer白名单一起使用。
    • 设置Referer白名单。

    细节分析如下:

    • 用户只有通过签名URL或者匿名访问object时,才会做防盗链验证。请求的Header中有“Authorization”字段的,不会做防盗链验证。
    • 一个Bucket可以支持多个Referer参数。
    • Referer参数支持通配符“*”和“?”。
    • 用户可以设置允许空Referer的请求访问。
    • 白名单为空时,不会检查Referer字段是否为空(不然所有的请求都会被拒绝,因为空Referer会被拒绝,对于非空Referer OSS在Referer白名单里也找不到)。
    • 白名单不为空,且设置了”不允许Referer字段为空”的规则。则只有Referer属于白名单的请求被允许,其他请求(包括Referer为空的请求)会被拒绝。
    • 白名单不为空,但设置了”允许Referer字段为空”的规则。则Referer为空的请求和符合白名单的请求会被允许,其他请求都会被拒绝。
    • Bucket的三种权限(private,public-read,public-read-write)都会检查Referer字段。

    通配符详解:

    • 星号“*”:可以使用星号代替0个或多个字符。如果正在查找以AEW开头的一个文件,但不记得文件名其余部分,可以输入AEW,查找以AEW开头的所有文件类型的文件,如AEWT.txt、AEWU.EXE、AEWI.dll等。要缩小范围可以输入AEW.txt,查找以AEW开头的所有文件类型并.txt为扩展名的文件如AEWIP.txt、AEWDF.txt。
    • 问号“?”:可以使用问号代替一个字符。如果输入love?,查找以love开头的一个字符结尾文件类型的文件,如lovey、lovei等。要缩小范围可以输入love?.doc,查找以love开头的一个字符结尾文件类型并.doc为扩展名的文件如lovey.doc、loveh.doc。
  • 不同的Referer设置和防盗链效果

    Referer 设置的效果如下:

    • 只设置“不允许referer为空”

      从控制台中来设置不允许referer为空,如图所示:

      直接访问:发现可以访问,是防盗链失效了吗?不是的,因为”白名单为空时,不会检查Referer字段是否为空”,所以白名单为空的时候,这个设置无效。因此需要设置Referer白名单。

    • 设置“不允许referer为空”的同时也设置Referer白名单

      从前面例子中我们可以看到在浏览器的请求中Referer为当前页面的URL,所以需要知道网站会从哪几个URL跳转过来,然后进行配置。

      Referfer白名单的设置规则:

      • 例子中的Referer为
        1
        http://userdomain/error.html

        ,所以Referer白名单可以设置为

        1
        http://userdomain/error.html

        ,但由于OSS的Referer检查是通过前缀匹配的,假如有其他网页比如

        1
        http://userdomain/index.html

        就访问不了,所以Referer白名单可以配置成

        1
        http://userdomain/

      • 假如还有其他域名比如
        1
        http://img.userdomain/index.html

        也需要访问,那么Referer白名单应该加上

        1
        http://*.userdomain/

      这里两个都配置,如图所示:

      做测试可以得到如下结果:

       
      浏览器输入 预期 结果
      http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png 直接访问,Referer为空,预期:不允许空Referer的请求,返回403。 符合预期
      http://userdomain/error.html 请求来自于源站,预期:访问成功。 符合预期
      http://userdomain-steal/index.html 请求来自于盗链网站,预期:OSS返回403,防盗链成功。 符合预期
      http://img.userdomain/error.html 请求来自于源站三级域名。 符合预期
      说明
      • 测试中提到的域名是为了测试而假设的,和您实际的域名不一样,请注意区分。
      • 如果Referer白名单只有
        1
        http://userdomain/

        ,浏览器模拟三级域名访问

        1
        http://img.userdomain/error.html

        的时候,三级域名无法匹配Referer白名单,OSS就会返回403。

    • 设置“允许referer为空”的同时也设置Referer白名单

      Referer白名单有

      1
      http://*.userdomain/

      1
      http://userdomain

      如图所示:

      测试得出以下结果:

       
      浏览器输入 预期 结果
      http://referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png 直接访问,Referer为空,预期:访问成功。 符合预期
      http://userdomain/error.html 请求来自于源站,预期:访问成功。 符合预期
      http://userdomain-steal/index.html 请求来自于盗链网站,预期:OSS返回403,防盗链成功。 符合预期
      http://img.userdomain/error.html 请求来自于源站三级域名。 符合预期
  • 如何在OSS里设置Referer

    功能使用参考:

  • Referer防盗链的缺点

    Referer防盗链的优点是设置简单,控制台即可操作。最大的缺点就是无法防止恶意伪造Referer,如果盗链是通过应用程序模拟HTTP请求,伪造Referer,则会绕过用户防盗链设置。如果对防盗链有更高要求,请参考以下签名URL防盗链的描述。

签名URL

签名URL的原理和实现方法见OSS开发人员指南授权第三方下载。签名URL的实现步骤如下:

  1. 将Bucket的权限设置为私有读。
  2. 根据期望的超时时间(签名URL失效的时间)生成签名。

具体实现方法如下:

  1. 安装PHP最新代码,参考PHP SDK文档
  2. 实现生成签名URL并将其放在网页中,作为外链使用的简单示例如下:
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    &lt;?php
     require 'vendor/autoload.php';
    <span class="hljs-meta"> #</span><span class="bash">最新PHP提供的自动加载</span>
     use OSS\OssClient;
    <span class="hljs-meta"> #</span><span class="bash">表示命名空间的使用</span>
    <span class="hljs-meta"> $</span><span class="bash">accessKeyId=<span class="hljs-string">"a5etodit71tlznjt3pdx7lch"</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">AccessKeyId,需要使用用户自己的</span>
    <span class="hljs-meta"> $</span><span class="bash">accessKeySecret=<span class="hljs-string">"secret_key"</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">AccessKeySecret,需要用用户自己的</span>
    <span class="hljs-meta"> $</span><span class="bash">endpoint=<span class="hljs-string">"oss-cn-hangzhou.aliyuncs.com"</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">Endpoint,根据Bucket创建的区域来选择,本文中是杭州</span>
    <span class="hljs-meta"> $</span><span class="bash">bucket = <span class="hljs-string">'referer-test'</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">Bucket,需要用用户自己的</span>
    <span class="hljs-meta"> $</span><span class="bash">ossClient = new OssClient(<span class="hljs-variable">$accessKeyId</span>, <span class="hljs-variable">$accessKeySecret</span>, <span class="hljs-variable">$endpoint</span>);</span>
    <span class="hljs-meta"> $</span><span class="bash">object = <span class="hljs-string">"aliyun-logo.png"</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">需要签名的Object</span>
    <span class="hljs-meta"> $</span><span class="bash">timeout = 300;</span>
    <span class="hljs-meta"> #</span><span class="bash">期望链接失效的时间,这里表示从代码运行到这一行开始的当前时间往后300秒</span>
    <span class="hljs-meta"> $</span><span class="bash">signedUrl = <span class="hljs-variable">$ossClient</span>-&gt;signUrl(<span class="hljs-variable">$bucket</span>, <span class="hljs-variable">$object</span>, <span class="hljs-variable">$timeout</span>); <span class="hljs-comment">#签名URL实现的函数</span></span>
    <span class="hljs-meta"> $</span><span class="bash">img= <span class="hljs-variable">$signedUrl</span>;</span>
    <span class="hljs-meta"> #</span><span class="bash">将签名URL动态放到图片资源中并打印出来</span>
    <span class="hljs-meta"> $</span><span class="bash">my_html = <span class="hljs-string">"&lt;html&gt;"</span>;</span>
    <span class="hljs-meta"> $</span><span class="bash">my_html .= <span class="hljs-string">"&lt;img src=""</span>.<span class="hljs-variable">$img</span>. <span class="hljs-string">"" /&gt;"</span>;</span>
    <span class="hljs-meta"> $</span><span class="bash">my_html .= <span class="hljs-string">"&lt;p&gt;"</span>.<span class="hljs-variable">$img</span>.<span class="hljs-string">"&lt;/p&gt;"</span>;</span>
    <span class="hljs-meta"> $</span><span class="bash">my_html .= <span class="hljs-string">"&lt;/html&gt;"</span>;</span>
     echo $my_html;
     ?&gt;
  3. 通过浏览器访问多请求几次会发现签名的URL会变,这是正常的。主要是因为过期时间的改变导致的。这个过期时间是链接失效的时间,是以Unix time的形式展示的。例如:Expires=1448991693,可以将这个时间转换成本地时间。在Linux下的命令为
    1
    date -d@1448991693

    ,也可以在网络上找工具自行转换。

特别说明

签名URL可以和Referer白名单功能一起使用。

如果签名URL失效的时间限制在分钟内,盗链用户即使伪造了Referer也必须拿到签名的URL,且必须在有效的时间内才能盗链成功。相比只使用Referer来说,增加了盗链的难度。也就是说签名URL配合Referer白名单功能,可以增加防盗链的效果。

防盗链总结

基于OSS的防盗链最佳实践点如下:

  • 使用三级域名URL,例如
    1
    referer-test.oss-cn-hangzhou.aliyuncs.com/aliyun-logo.png

    安全性比绑定二级域名更高。三级域名方式能够提供Bucket级别的清洗和隔离,能够应对被盗链后的流量暴涨的情况,也能避免不同Bucket间的互相影响,最终提高业务可用性。

  • 如果使用自定义域名作为连接,CNAME也请绑定到三级域名,规则是bucket + endpoint。假如您的bucket名为test,三级域名则为
    1
    test.oss-cn-hangzhou.aliyuncs.com

  • 对Bucket设定尽可能严格的权限类别。例如提供公网服务的Bucket设置为public-read或private,禁止设置为public-read-write。Bucket权限参见访问控制
  • 对访问来源进行验证,根据需要设置合适的Referer白名单。
  • 如果需要更严格的防盗链方案,请参考签名的URL方案。
  • 记录Bucket访问日志,能够及时发现盗链活动和验证防盗链方案的有效性。访问日志参见设置访问日志记录

常见问题及解决方案

  • 在OSS控制台设置了防盗链,但一直不生效,页面可以反而播放器不可以,请问为什么?怎么解决?

    目前设置防盗链不生效的主要问题集中于视频和音频文件,在使用诸如Windows Media Player、Flash Player等播放器后,在请求OSS资源的时候传递的Referer为空,这就造成防盗链的失效。针对这种情况,可以参考上面提到的签名URL防盗链的方法。

  • Referer是什么?如果遇到HTTPS怎么办?

    Referer是HTTP协议中的请求头,在跨页面访问的时候会带上。需要看看浏览器请求的Referer是

    1
    http://

    还是

    1
    https://

    ,通常是

    1
    http://

  • 如何生成签名URL?AccessKeySecret放在客户端里的安全性如何?

    签名URL的方法参见各个SDK文档。AccessKeySecret不建议直接放在客户端,RAM提供了STS服务可以解决这个问题,详情参见RAM和STS指南

  • 例如要写
    1
    a.baidu.com

    1
    b.baidu.com

    ,这两个用通配符(*,?) 如何写?

    可以写成

    1
    http://*.baidu.com

    ,对于这种单字符,也可以写成

    1
    http://?.baidu.com

  • *.domain.com 可以匹配二级域名,但无法匹配 domain.com,另外添加一行 domain.com 也没效果,如何配置?

    注意一般的referer中会带http这样的参数,可以通过chrome的开发者模式观察下请求的Referer是什么,然后再具体设置。这里可能是忘了写

    1
    http://

    ,应该为

    1
    http://domain.com

  • 如果防盗链没有生效怎么办?

    推荐使用chrome来查看。打开开发者模式,点击网页,查看HTTP请求中的

    1
    Referer

    具体值。并确认是否与OSS中设置的

    1
    Referer

    匹配。如果还是解决不了,请提工单。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: