防盗链有很多方案,比如一些网盘经常会对下载链接做手脚,给用户看到的链接都是处理过的,像skydriver生成的下载链接中就会包含一个时间戳的加密字符串,你拿到的url链接过了一天就废掉了(我也是刚发现这个问题,以前给别人的链接都不能用了。。。)。
不过本篇谈的是防图片盗链,图片资源在一个站点上被访问的频率应该是相当多的,如果采用timespan、token之类的方案对性能也是一种浪费,所以应该采用速度快又能防住大多数盗链情形的方法。我们模仿sina这种大站的做法,通过判断request响应头中的Refferer来筛选盗链连接。
(注意:Refferer是很容易被伪造的,所以对于重要资源还是应该使用其他方案)
效果预览
图一:站内引用图片资源,能够正常显示。图二:浏览器直接输入图片地址,被重定向到反盗链提示页面。图三:站外引用,被重定向到反盗链页面。
实现
a.通过自定义HttpHandler来处理对指定类型文件的处理,在ProcessRequest(HttpContext context)方法中判断Refferer,如果Refferer为空,说明是直接访问(比如用户将图片地址直接粘贴在地址栏中访问),如果Refferer的域名与Request中的域名相同说明是站内引用,如果Refferer的域名是伙伴站点,那么也应该允许,除此以外就应该拒绝。
b.关于参数配置:为实现逻辑与具体数据、参数之间的解耦,我们可以把伙伴站点列表以及重定向地址放在web.config配置文件中,这可以通过自定义ConfigurationSection来实现。
代码
CheckReffererHandler
{
/// <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
}
}
配置文件部分节选
< 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 >
自定义配置节相关代码
{
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; }
}
}
}
源代码下载链接:
http://files.cnblogs.com/wJiang/refferer.rar