代码改变世界

基于Refferer的防盗链方案

2010-04-04 23:20  姜 萌@cnblogs  阅读(2588)  评论(4编辑  收藏  举报

google、baidu的图片搜索中常看到有些图片上面写着诸如“此图片仅供……用户交流”,说明这些网站是已经发现了我们浏览器发出的请求为站外链接(而且是非伙伴站点的链接)。

防盗链有很多方案,比如一些网盘经常会对下载链接做手脚,给用户看到的链接都是处理过的,像skydriver生成的下载链接中就会包含一个时间戳的加密字符串,你拿到的url链接过了一天就废掉了(我也是刚发现这个问题,以前给别人的链接都不能用了。。。)。

不过本篇谈的是防图片盗链,图片资源在一个站点上被访问的频率应该是相当多的,如果采用timespan、token之类的方案对性能也是一种浪费,所以应该采用速度快又能防住大多数盗链情形的方法。我们模仿sina这种大站的做法,通过判断request响应头中的Refferer来筛选盗链连接。

(注意:Refferer是很容易被伪造的,所以对于重要资源还是应该使用其他方案)

效果预览

imageimage image

图一:站内引用图片资源,能够正常显示。图二:浏览器直接输入图片地址,被重定向到反盗链提示页面。图三:站外引用,被重定向到反盗链页面。

实现

a.通过自定义HttpHandler来处理对指定类型文件的处理,在ProcessRequest(HttpContext context)方法中判断Refferer,如果Refferer为空,说明是直接访问(比如用户将图片地址直接粘贴在地址栏中访问),如果Refferer的域名与Request中的域名相同说明是站内引用,如果Refferer的域名是伙伴站点,那么也应该允许,除此以外就应该拒绝。

b.关于参数配置:为实现逻辑与具体数据、参数之间的解耦,我们可以把伙伴站点列表以及重定向地址放在web.config配置文件中,这可以通过自定义ConfigurationSection来实现。

代码

CheckReffererHandler

CheckReffererHandler
namespace Sopaco.Library.Web.AntiForgery
{
    
/// <summary>
    
/// 检查是否为外站连接
    
/// </summary>
    public class CheckReffererHandler : IHttpHandler, IRequiresSessionState
    {
        
#region Fields
        
private string _redirectPath = null;
        
private static IEnumerable<string> _acceptedHostList;
        
#endregion

        
#region Constructors & Initializer
        
public CheckReffererHandler()
        {
            Init();
        }
        
private void Init()
        {
            
if(_acceptedHostList == null)
                rebuildAcceptedList();
        }
        
#endregion

        
#region IHttpHandler 成员

        
public bool IsReusable
        {
            
get { return false; }
        }

        
public void ProcessRequest(HttpContext context)
        {
            Uri uri 
= context.Request.UrlReferrer;
            
if (uri == null)//如果是直接引用
                SendBackToForgery(context);
            
else if (CheckIsHostSame(uri, context.Request.Url))//如果是站内引用
                SendAcceptBack(context, context.Request.Path);
            
else if(CheckFromAcceptedHost(uri))//对于站外引用,检查是否来源于伙伴站点
                SendAcceptBack(context, context.Request.Path);
            
else//对于非伙伴站点的处理
                SendBackToForgery(context);
        }

        
#endregion
        
#region protected Helper Methods
        
private bool CheckFromAcceptedHost(Uri uri)
        {
            
return _acceptedHostList.Where(p => p.Equals(uri.Host, StringComparison.InvariantCultureIgnoreCase)).Count() > 0;
        }

        
private bool CheckIsHostSame(Uri host0, Uri host1)
        {
            
return host0.Host.Equals(
                host1.Host, StringComparison.InvariantCultureIgnoreCase);
        }
        
private void SendBackToForgery(HttpContext context)
        {
            
if(_redirectPath == null)
            {
                _redirectPath 
= ConfigurationManager.AppSettings["backToForgeryUrl"];
                
if (_redirectPath == null)
                {
                    context.Response.StatusCode 
= 404;
                    
return;
                }
            }
            context.Response.Redirect(_redirectPath);
        }
        
private void SendAcceptBack(HttpContext context, string virtualPath)
        {
            
string filePath = context.Server.MapPath(virtualPath);
            
if (!File.Exists(filePath))
            {
                context.Response.StatusCode 
= 404;
                context.Response.StatusDescription 
= "未找到文件:" + virtualPath;
                
return;
            }
            context.Response.WriteFile(filePath);
        }
        
private static void rebuildAcceptedList()
        {
            SimpleSection section 
= ConfigurationManager.GetSection("checkReffererHandler"as SimpleSection;
            _acceptedHostList 
= section.Items.Items.Select(p => p.Value);
        }
        
#endregion
    }
}

 

配置文件部分节选

 

web.config节选
<!--声明自定义配置节 -->
    
<section name="checkReffererHandler" 
             type
="Sopaco.Library.EnterLib.ConfigurationSections.SimpleSection, Sopaco.Library.EnterLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
             allowLocation
="true" allowDefinition="Everywhere" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true" 
             requirePermission
="true" />

<!--配置数据-->
  
<checkReffererHandler>
    
<items>
      
<!--以下为允许外站引用的站点清单-->
      
<add name="1" value="www.sopaco.net" />
      
<add name="2" value="www.cnblogs.com" />
      
<add name="3" value="live.spaces.com" />
    
</items>
  
</checkReffererHandler>

 

自定义配置节相关代码

 

自定义配置节相关代码
namespace Sopaco.Library.EnterLib.ConfigurationSections
{
    
public class SimpleSection : ConfigurationSection
    {
        [ConfigurationProperty(
"items")]
        [ConfigurationCollection(
typeof(SimpleItemsElement))]
        
public SimpleItemsElement Items
        {
            
get { return base["items"as SimpleItemsElement; }
            
set { base["items"= value; }
        }
    }
}
namespace Sopaco.Library.EnterLib.ConfigurationSections
{
    
public class SimpleItemsElement : ConfigurationElementCollection
    {
        
#region Fields
        List
<SimpleItemElement> _items;
        
#endregion
        
protected override ConfigurationElement CreateNewElement()
        {
            
return new SimpleItemElement();
        }

        
protected override object GetElementKey(ConfigurationElement element)
        {
            
return (element as SimpleItemElement).Name;
        }

        
public IList<SimpleItemElement> Items
        {
            
get
            {
                
if (_items == null)
                {
                    _items 
= new List<SimpleItemElement>();
                    RefreshItems();
                }
                
return _items;
            }
        }

        
public void RefreshItems()
        {
            _items.Clear();
            
for(int i = 0; i < base.Count; i+=1)
            {
                _items.Add(
base.BaseGet(i) as SimpleItemElement);
            }
        }
    }
}
namespace Sopaco.Library.EnterLib.ConfigurationSections
{
    
public class SimpleItemElement : ConfigurationElement
    {
        [ConfigurationProperty(
"name")]
        
public string Name
        {
            
get { return base["name"as string; }
            
set { base["name"= value; }
        }
        [ConfigurationProperty(
"value")]
        
public string Value
        {
            
get { return base["value"as string; }
            
set { base["value"= value; }
        }
    }
}

 

源代码下载链接:

 https://files.cnblogs.com/wJiang/refferer.rar