文/李信棟
前幾天隨手寫的 chrome 外掛遇到了防盜鏈問題,由於外掛不能用 js iframe 的方法反防盜鏈,於是想用伺服器做箇中轉。記錄一下上手專案的各個點,以後再用 nodejs 就不用到處查資料了。
之前沒有一套特別熟悉的 web 開發框架,加上外掛儲存服務依賴的平臺 LeanCloud 剛好支援部署 nodejs 網站,剛好拿這個小專案作為 nodejs 上手專案。
怎麼「破解防盜鏈」呢?
想要破解,就得先知道目標——防盜鏈如何實現。
大多數站點的策略很簡單: 判斷request請求頭的refer是否來源於本站。若不是,拒絕訪問真實圖片。而我們知道: 請求頭是來自於客戶端,是可偽造的。
思路
那麼,我們偽造一個正確的refer來訪問不就行了?
整個業務邏輯大概像這樣:
- 自己的伺服器後臺接受帶目標圖片url引數的請求
- 偽造refer請求目標圖片
- 把請求到的資料作為response返回
這就起到了圖片中轉的作用。
1. 專案是什麼樣子
1.1 介面的樣子?
- 有一個開放介面
- 介面有一個引數,api?url=abc.com/image.png,大…
- 響應內容是反防盜鏈後的真實圖片
1.2 應該怎麼做?
- 把伺服器跑起來
- 處理 GET 請求
- 分析請求引數
- 下載原圖
- response 原圖
2. 學習路徑(在對目標未知的前提下提出疑問)
- 如何開始,建立伺服器
- 如何處理基本請求 GET POST
- 如何下載圖片並轉發
- 完成基本功能,上線
- 優化
2.1 如何開始,建立伺服器
主要是 http.createServer().listen(port) 這組方法,建立伺服器、監聽埠一鍵搞定。
var http = require(`http`);
http.createServer(function (request, response) {
// do things here
}).listen(8888);
console.log(`Server running at: 8888`);複製程式碼
2.2 如何處理基本請求 GET POST
createServer 回撥方法的兩個引數 req res 是 http request 和 response 的內容,列印一下他們的內容。
request 是 InComingMessage 類,列印它的 url 欄位。
var http = require(`http`);
var url = require(`url`);
var util = require(`util`);
http.createServer(function(req, res){
res.writeHead(200, {`Content-Type`: `text/plain`});
res.end(util.inspect(url.parse(req.url, true)));
}).listen(3000);複製程式碼
請求
http://localhost:3000/api?url=http://abc.com/image.png
請求結果
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: `?url=http://abc.com/image.png`,
query: { url: `http://abc.com/image.png` },
pathname: `/api`,
path: `/api?url=http://abc.com/image.png`,
href: `/api?url=http://abc.com/image.png` }複製程式碼
query 欄位剛好是我們想要的內容,下載這個欄位對應的圖片。
2.3 如何下載圖片並轉發
request 模組支援管道方法,可以和 shell 的管道一樣理解。
這可以省很多事,不需要在本地儲存圖片,不需要處理雜七雜八的事情,甚至不需要再去了解 nodejs 的流。一個方法全搞定。
關鍵方法: request(options).pipe(res)
var options = {
uri: imgUrl, // 這個 uri 為空時,會認為該欄位不存在,報異常
headers: {
`Referer`: referrer // 解決部分防盜鏈選項
}
};
request(options).pipe(res);複製程式碼
2.4 完成基本功能,上線
完整程式碼
`use strict`;
var router = require(`express`).Router();
var http = require(`http`);
var url = require(`url`);
var util = require(`util`);
var fs = require(`fs`);
var callfile = require(`child_process`);
var request = require(`request`);
router.get(`/`, function(req, res, next) {
var imgUrl = url.parse(req.url, true).query.url;
console.log(url.parse(req.url,true).query);
console.log(`get a request for ` + imgUrl);
if (imgUrl == null || imgUrl == "" || imgUrl == undefined) {
console.log(`end`);
res.end();
return;
}
var parsedUrl = url.parse(imgUrl);
// 這裡暫時使用圖片伺服器主機名做Referer
var referrer = parsedUrl.protocol + `//` + parsedUrl.host;
console.log(`referrer ` + referrer);
var options = {
uri: imgUrl,
headers: {
`Referer`: referrer
}
};
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
console.log("type " + response.headers[`content-type`]);
}
res.end(response.body);
}
// request(options, callback);
request(options)
.on(`error`, function(err) {
console.log(err)
})
.pipe(res);
});
module.exports = router;複製程式碼
2.5 優化
這部分主要是防盜鏈部分的優化。
單就 Referer 來說,使用空值和主機名都只能滿足部分需求。
一個優化方式是組合,當一種方式不能突破即採用另一種方式。
這種方式的有點在於擴大了適用面積,並且方法對任何場景比較通用。
一個優化方式是介面請求引數帶源引用連線。這種方式對很多人來說不太通用,因為很多場景下並不清楚源引用連線在哪。但是對我的外掛來說非常適用,外掛本身保留了源引用。因此可以很好的繞過防盜鏈限制。