基于Refferer的防盗链方案
2010-04-04 23:20 姜 萌@cnblogs 阅读(2590) 评论(4) 编辑 收藏 举报google、baidu的图片搜索中常看到有些图片上面写着诸如“此图片仅供……用户交流”,说明这些网站是已经发现了我们浏览器发出的请求为站外链接(而且是非伙伴站点的链接)。
防盗链有很多方案,比如一些网盘经常会对下载链接做手脚,给用户看到的链接都是处理过的,像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; }
}
}
}
源代码下载链接: