本文同步在我的博客shymean.com上,欢迎关注javascript
最近处理了一个与referer有关的需求,发现里面仍是有一点门道的。所以在本篇文章整理了referer相关知识点,主要涉及图片防盗链与如何绕开防盗链限制。php
参考:html
Referer
是HTTP请求头的一个字段,包含了当前请求页面的来源页面的地址,经过该字段,咱们能够检测访客是从哪里来的。前端
那么,referer到底有啥做用呢?java
在某些web应用的交互中,右上角会提供一个返回按钮,方便用户返回上一页 nginx
其实现通常也比较简单web
<a href="javascript: history.back();"></a>
复制代码
这种处理方式隐藏的一个问题是:若是用户从其余入口如分享连接等地方直接进来时,点击这个按钮是没法返回。ajax
所以在点击按钮时,咱们能够判断document.referrer
是否存在来优化交互:若是存在,则返回上一页;若是不存在,则直接返回首页。chrome
应该注意到上面写的是referer
,而在DOM中,使用的是referrer
,这是所以请求头中的referer是因为历史缘由致使的拼写错误,而在DOM规范中进行了修正,所以致使当前拼法并不统一的问题~segmentfault
当用户访问网页时,referer就是前一个网页的URL;若是是图片的话,一般指的就是图片所在的网页。当浏览器向服务器发送请求时,referer就自动携带在HTTP请求头了。
一个HTML页面每每包含多种资源,这些资源经过标签如script
、img
、link
等形式嵌套在HTML文档中,一个完整页面每每须要通过发送多条HTTP请求下载资源,而后才能正常展现。因为HTML自己并无对嵌套资源的来源作限制,基于这样的机制,盗链就成为了一种手段。
下面是关于盗链的百度百科定义
盗链是指服务提供商本身不提供服务的内容,经过技术手段绕过其它有利益的最终用户界面(如广告),直接在本身的网站上向最终用户提供其它服务提供商的服务内容,骗取最终用户的浏览和点击率。受益者不提供资源或提供不多的资源,而真正的服务提供商却得不到任何的收益。
打个比方:A网站将本身的静态资源如图片或视频等存放在服务器上。B网站在未经A容许的状况下,使用A网站的图片或视频资源,放置到本身的网站中。因为服务器资源是须要花钱的,这样网站B盗取了网站A的空间和流量,而A没有获取任何利益却承担了资源使用费。B盗用A资源放到本身网站的行为即为盗链。
防盗链通常由下面几种方式
这里咱们主要关注一下referer的防盗链的原理。下面是nginx的防盗链配置
location ~* \.(gif|jpg|png)$ {
valid_referers none blocked *.phptest.com;
if ($invalid_referer) {
return 403;
}
}
复制代码
这种方法是在server或者location段中加入:valid_referers
。这个指令在referer头的基础上为 $invalid_referer
变量赋值,其值为0或1。若是valid_referers
列表中没有Referer头的值, $invalid_referer
将被设置为1。
该指令支持none
和blocked
,
经过referer,咱们能够判断请求的来源,从而决定服务器是否正常返回请求资源,达到控制请求的目的。
须要注意的是,在某些状况下,即便用户是正常访问网页或图片,也是不会携带referer的,好比直接在浏览器地址栏直接输入资源URL,或经过浏览器新窗口打开页面等。这种访问是正常的,若是强制如今某些白名单referer名单才能访问资源,则可能误伤这一部分正经常使用户,这也是为何有的防盗链检测中容许Referer头部为空经过检测的状况。
既然如此,若是把referer隐藏掉,也能够绕开部分站点防盗链的限制,下面让咱们来看看如何实现隐藏referer的功能。
参考
在利用部分站点防盗链限制容许referer为空,或者咱们仅仅是不想让服务器知道访问来源时,咱们能够隐藏referer。
以前浏览器在请求资源时,会按本身的默认规则来决定是否加上Referrer。后来W3C发布了Referrer-Policy
草案,运行开发者灵活地控制本身网站的referer策略。主要包含下面策略
上面只列举了一部分可选策略,详情可参考MDN文档
。
所以,咱们能够手动指定no-referrer
来隐藏referer
<!-- phptest2.com是我本地的一个测试域名, 下同 -->
<img src="http://phptest2.com/upload/1.png" width="200" referrerPolicy="no-referrer" alt="">
复制代码
或者在建立image对象的时候,指定referrerPolicy
策略
const img = new Image()
img.referrerPolicy = 'no-referrer'
复制代码
此时打开开发者工具就能够看见该图片的请求已经再也不携带对应的referer了。总结一下,通常有下面几种设置Referer策略的方式:
须要注意的是目前referrerPolicy
仍处于提案的草稿阶段,浏览器兼容性并非特别好。
XMLHttpRequest对象提供了setRequestHeader
方法,用于向请求头添加或修改字段。咱们能不能手动将修改 referer字段呢?
// 经过ajax下载图片
function loadImage(uri) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function() {
resolve(xhr.response);
};
xhr.open("GET", uri, true);
xhr.setRequestHeader("Referer", ""); // 经过setRequestHeader设置header不会生效
xhr.send();
});
}
// 将下载下来的二进制大对象数据转换成base64,而后展现在页面上
function handleBlob(blob) {
let reader = new FileReader();
reader.onload = function(evt) {
img.src = evt.target.result;
};
reader.readAsDataURL(blob);
}
const imgSrc = "http://phptest2.com/upload/1.png";
loadImage(imgSrc).then(blob => {
handleBlob(blob);
});
复制代码
上述代码运行时会发现控制台提示错误
Refused to set unsafe header "Referer"
能够看见setRequestHeader
设置referer
响应头是无效的,这是因为浏览器为了安全起见,没法手动设置部分保留字段,不幸的是Referer
刚好就是保留字段之一,详情列表参考Forbidden header name。
所以在经过AJAX设置referer宣告失败,那咱们能够换一个方式从浏览器加载图片,好比试试Fetch
呢?
Fetch
是浏览器提供的一个全新的接口,用于访问和操做HTTP管道部分,该接口支持referrerPolicy
,所以也能够用来操做referer。
function fetchImage(url) {
return fetch(url, {
headers: {
// "Referer": "", // 这里设置无效
},
method: "GET",
mode: "cors",
redirect: "follow",
referrer: "" // 将referer置空,此处写成no-referrer貌似会把路径替换成 host + 'no-referrer'字符串形式
}).then(response => response.blob());
}
loadImage(imgSrc).then(blob => {
handleBlob(blob);
});
复制代码
经过将配置参数redirect
置位空,能够看见本次请求已经不带referer了。
下面是一种经过iframe来实现隐藏referer的方式,,整个过程有点魔性。大体实现以下
const putNoRefererImage = (() => {
let iframe
/* src: 图片地址 wrap:须要加载图片的容器 */
return function (src, wrap) {
if (iframe) {
iframe.remove()
}
let url = new URL(src);
let frameid = 'frameimg' + Math.random();
window.img = `<img id="tmpImg" width=400 src="${url}" alt="图片加载失败,请稍后再试"/> `;
// 构造一个iframe
iframe = document.createElement('iframe')
iframe.id = frameid
iframe.src = "javascript:parent.img;" // 经过内联的javascript,设置iframe的src
// 校订iframe的尺寸,完整展现图片
iframe.onload = function () {
var img = iframe.contentDocument.getElementById("tmpImg")
if (img) {
iframe.height = img.height + 'px'
iframe.width = img.width + 'px'
}
}
iframe.width = 200
iframe.height = 200
iframe.scrolling = "no"
iframe.frameBorder = "0"
wrap.appendChild(iframe)
}
})();
putNoRefererImage(imgSrc, document.body);
复制代码
运行代码能够看见,经过这种方式也能够实现隐藏referer的功能,所以用做不支持referrerPolicy
的一种替代方案。
在某些不支持javascript内联运行的场景下,这种方案也是不可行的,好比在chrome扩展程序,因为content_security_policy
,使用内联JavaScript会报错
Refused to execute JavaScript URL because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
本文总结了referer的做用,以及利用referer实现防盗链的配置。因为部分站点的防盗链配置容许referer为空,所以能够利用这一点,经过隐藏referer,来达到绕开防盗链的目的。
接着介绍了几种前端隐藏referer的实现方式,从技术上来看,referrerPolicy
是近乎完美的选择,因为存在兼容性限制,所以能够经过fetch
或iframe
等方式来实现。
了解了防盗链,以及如何绕开防盗链,咱们才能更好的保证本身站点资源的安全性。除了采用referer防盗链,咱们还能够采用身份认证、按期修改资源路径等方式避免盗链。最后,用一句老话结束:
当你凝视深渊的时候 深渊也在凝视你。