本文同步在我的博客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防盜鏈,咱們還能夠採用身份認證、按期修改資源路徑等方式避免盜鏈。最後,用一句老話結束:
當你凝視深淵的時候 深淵也在凝視你。