閱讀原文
什麼是 “盜鏈”?
“盜鏈” 說白了就是利用別人網站的資源連結放在自己的站點,在未經允許的情況下去獲取別人網站裡面的圖片或者影片等資源,導致資源所有者的網站的流量費用增加或收入減少,為了防止資源連結隨意被人盜用的手段被稱為 “防盜鏈”。
模擬 “盜鏈” 場景
我們先來模擬一下 “盜鏈” 場景,在本地啟動服務執行 hotlinking.html
檔案,並在檔案中盜用百度影片的圖片資源,看看效果。
<!-- 檔案:hotlinking.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>盜鏈</title>
</head>
<body>
<img src="http://c.hiphotos.baidu.com/video/pic/item/e61190ef76c6a7ef8891a7c9f1faaf51f2de66ad.jpg">
</body>
</html>
我們透過 http-server
來啟動伺服器訪問 hotlinking.html
,使用 http-server
需全域性安裝。
http-server install -g
在服務中開啟 hotlinking.html
後我們發現圖片並不是我們盜用連結的資源,而是變成了下面這張圖片。
這張圖用來提醒我們盜用了別人資源,是因為百度的伺服器做了防盜鏈處理,如果所有盜用別人的資源都變成這樣,盜用也就沒有實際意義了,我們本篇就透過 NodeJS 來實現防盜鏈處理,用來保護自己站點的資源。
注意:具備防盜鏈處理的網站的資源連結可以直接透過瀏覽器位址列訪問,也可以在檔案域(file 協議)訪問,限制的是在未經允許的情況下其他伺服器的訪問。
NodeJS 伺服器實現防盜鏈
1、模擬兩個域名
在本地的 hosts 檔案中加入兩個域名:
127.0.0.1 panda.com
127.0.0.1 shen.com
2、準備圖片資源
在根目錄建立資料夾 public
並存入兩張圖片,success.png
是正常請求的圖片資源,error.png
是經過防盜鏈處理後返回的圖片資源,兩張圖片如下。
正常返回的圖片資源 success.png:
防盜鏈處理後返回的圖片資源 error.png:
3、頁面 index.html
在頁面當中透過 img
標籤分別訪問 shen.com、panda.com 和 localhost 域下的 success.png
檔案。
<!-- 檔案:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>盜鏈</title>
</head>
<body>
<img src="http://panda.com:3000/success.png">
<img src="http://shen.com:3000/success.png">
<img src="http://localhost:3000/success.png">
</body>
</html>
4、服務端 server.js
在寫服務端程式碼之前需要介紹兩個重要的請求頭:
- host:資源所在的域
- referer:請求來源的域
其實資源防盜就是設定白名單,透過檢測 referer
是否在白名單中,如果在則正常返回資源,不存在則返回經過防盜鏈處理的資源。
注意:referer 請求頭在位址列輸入地址時傳送的請求是不存在的(如請求 index.html 頁面),在舊版本的 HTTP 協議中 referer
的寫法為 referered
,所以為了相容舊版本協議應該做相容處理。
// 檔案:server.js
// 引入依賴
const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("mz/fs");
const server = http.createServer(responseImages); // 建立伺服器
let static = path.resolve(__dirname, "public"); // 靜態資源目錄
let whiteList = ["shen.com"]; // 白名單
async function responseImages(req, res) {
// 解析 url 中的檔案目錄處理成絕對路徑
let p = path.join(static, url.parse(req.url).pathname);
// 檢測檔案路徑是否合法,不合法直接返回 Not Found
let isExist = await fs.exists(p);
if (isExist) {
// 獲取 referer
let refer = req.headers["referer"] || req.headers["referered"];
// 存在 referer 繼續檢測
if (refer) {
// 請求資源存在 referer,做防盜鏈處理
let referHost = url.parse(refer).hostname;
let host = req.headers["host"].split(":")[0];
// 當訪問源的域和資源所在的域不是同一個域,做防盜鏈處理
if (referHost !== host) {
let isInWhiteList = whiteList.includes(refer);
p = isInWhiteList ? p : path.join(static, "error.png");
}
}
// 第一次訪問請求頁面 index.html,不存在 referer,將靜態資源返回
// 第二次訪問請求圖片資源,如果 referer 和資源所本就是同一個域,直接將資源返回
fs.createReadStream(p).pipe(res);
} else {
res.statusCode = 404;
res.end("Not Found");
}
}
server.listen(3000, () => {
console.log("server start 3000");
});
其實上面的伺服器是 shen.com、panda.com 和 localhost 所共用的,只是透過不同的域名訪問。
啟動伺服器,然後透過 localhost:3000 訪問,此時由於與 shen.com 和 panda.com 為不同域,所以只有第三張圖片返回 success.png
。
透過 shen.com:3000 訪問,由於存在白名單中,所以三張圖片都返回 success.png
。
透過 panda.com:3000 訪問,由於 shen.com 在不同域,所以沒有返回 success.png
。
無論透過 shen.com 還是 panda.com 訪問 localhost 的資源都是在同域的,所以都能獲取到。
總結
在上面我們利用本地服務實現了一個最基本的防盜鏈,思路就是 referer
與資源同域,正常返回,不同域檢測白名單,在真實的開發場景可能會更細化,更復雜一些,其實整個防盜鏈實現的核心就是利用 HTTP 的 referer
和 host
請求頭做檢測,希望透過本篇的學習,大家可以對資源防盜鏈有所瞭解,並在後面開發類似功能時提供思路。