Referer

Referer申请头蕴含了以后申请页面的起源页面的地址,即示意以后页面是通过此起源页面里的链接进入的。服务端个别应用Referer(注:正确英语拼写应该是referrer,因为晚期HTTP标准的拼写错误,为了放弃向后兼容就始终连续下来)申请头辨认拜访起源,可能会以此统计分析、日志记录以及缓存优化等。

 注: Referer申请头可能会裸露用户的浏览历史、波及到用户的隐衷问题。

Referrer-policy

Referrer-policy作用就是为了管制申请头中referer的内容

蕴含以下值:

  • no-referrer : 整个referee首部会被移除,拜访起源信息不随着申请一起发送。
  • no-referrer-when-downgrade : 在没有指定任何策略的状况下用户代理的默认行为。在等同安全级别的状况下,援用页面的地址会被发送(HTTPS->HTTPS),然而在降级的状况下不会被发送 (HTTPS->HTTP).
  • origin: 在任何状况下,仅发送文件的源作为援用地址。例如  https://example.com/page.html 会将 https://example.com/ 作为援用地址。
  • origin-when-cross-origin: 对于同源的申请,会发送残缺的URL作为援用地址,然而对于非同源申请仅发送文件的源。
  • same-origin: 对于同源的申请会发送援用地址,然而对于非同源申请则不发送援用地址信息。
  • strict-origin: 在等同安全级别的状况下,发送文件的源作为援用地址(HTTPS->HTTPS),然而在降级的状况下不会发送 (HTTPS->HTTP)。
  • strict-origin-when-cross-origin: 对于同源的申请,会发送残缺的URL作为援用地址;在等同安全级别的状况下,发送文件的源作为援用地址(HTTPS->HTTPS);在降级的状况下不发送此首部 (HTTPS->HTTP)。
  • unsafe-url: 无论是同源申请还是非同源申请,都发送残缺的 URL(移除参数信息之后)作为援用地址。(最不平安了)

浏览器兼容性(https://caniuse.com/?search=r...):

如何设置referer

  1. 在HTML里设置meta 

    <meta name="referrer" content="origin">

    如下图:

  2. 或者用\<a>、\<area>、\<img>、\<iframe>、\<script> 或者 \<link> 元素上的 referrerpolicy 属性为其设置独立的申请策略。
     
    如:
<script src='/javascripts/test.js' referrerpolicy="no-referrer"></script>



 

未加referrerpolicy属性的link元素:

盗链

盗链是指在本人的页面上展现一些并不在本人服务器上的一些内容, 获取他人的资源地址,绕过他人的资源展现页面,间接在本人的页面上向最终用户提供此内容。 个别被盗链的都是图片、 可执行文件、 音视频文件、压缩文件等资源。通过盗链的伎俩能够加重本人服务器的累赘

比方在本人页面里引入百度贴吧里的一张照片:

<body>    <img src="https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg"> </body>

但实际上是无奈展现的(如下图),之所以无奈展现是因为百度的图片做过防盗链解决

防盗链的工作原理

通过Referer或者签名,网站能够检测指标网页拜访的起源网页,如果是资源文件,则能够追踪到显示它的网页地址 一旦检测到起源不是本站,即进行阻止或者返回指定的页面

绕过图片防盗链

那么当初的很多网站是如何利用referer来进行防图片盗链的呢?
三种状况下容许援用图片:

  1. 本网站。
  2. 无referer信息的状况。(服务器认为是从浏览器间接拜访的图片URL,所以这种状况下能失常拜访)
  3. 受权的网址。

咱们只能从状况2动手,通过设置referer为空进行绕过防盗链。

利用https网站盗链http资源网站,refer不会发送

先利用openssl生成自签名证书(具体可看https://github.com/zxl9257686...)
client.js

let https = require("https");let fs = require("fs");let url = require("url");let path = require("path");var options = {  hostname: "localhost",  port: 8000,  path: "/",  method: "GET",  rejectUnauthorized: false,  key: fs.readFileSync("./keys/client.key"),  cert: fs.readFileSync("./keys/client.crt"),  ca: [fs.readFileSync("../ca/ca.crt")],};// 创立服务器https.createServer(options, function (req, res) {  let staticPath = path.join(__dirname, "src");  let pathObj = url.parse(req.url, true);  if (pathObj.pathname === "/") {    pathObj.pathname += "index.html";  }  //  读取动态目录外面的文件,而后发送进来  let filePath = path.join(staticPath, pathObj.pathname);  fs.readFile(filePath, "binary", function (err, content) {    if (err) {      res.writeHead(404, "Not Found");      res.end("<h1>404 Not Found</h1>");    } else {      res.writeHead(200, "Not Found");      res.write(content, "binary");      res.end();    }  });}).listen(8080);

index.html

<div id="container">    <img src="http://localhost:9999"></div>

启动后果如下:
提醒: 因为咱们应用了自签名的证书,拜访页面时可能会看到浏览器的证书正告,可能须要手动点击信赖以后证书,或者手动点击链接确认拜访该页面。例如Chrome 揭示“您的连贯不是私密连贯”,并禁止你拜访。你能够间接在以后页面输出 thisisunsafe,不是在地址栏输出,而是间接敲击键盘输入,页面会主动刷新进入网页。

设置meta

<meta name="referrer" content="no-referrer" />

设置referrerpolicy="no-referrer"

以上已验证过,只是存在局部兼容性问题。

https://images.weserv.nl/?url=${你的图片地址}

因为网址是国外的速度有点慢成果还行,目标就是返回一个不受限制的图片,然而 GIF 格局会返回jpg也就是没有了动画成果。

利用iframe伪造申请referer

内容参考 https://juejin.cn/post/684490...

function showImg(src, wrapper ) {    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 = 10    iframe.height = 10    iframe.scrolling = "no"    iframe.frameBorder = "0"    wrapper.appendChild(iframe)}showImg('https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg', document.querySelector('#container'))

后果如下:

客户端在申请时批改header头部

内容参考 https://juejin.cn/post/684490...

利用XMLHttpRequest

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);        // 通过setRequestHeader设置header不会失效        // 会提醒 Refused to set unsafe header "Referer"        xhr.setRequestHeader("Referer", "");         xhr.send();    });}  // 将下载下来的二进制大对象数据转换成base64,而后展现在页面上function handleBlob(blob) {    let reader = new FileReader();    reader.onload = function(evt) {        let img = document.createElement('img');        img.src = evt.target.result;        document.getElementById('container').appendChild(img)    };    reader.readAsDataURL(blob);}const imgSrc = "https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg";loadImage(imgSrc).then(blob => {    handleBlob(blob);});

上述代码运行时会发现控制台提醒谬误:

Refused to set unsafe header "Referer"

能够看见setRequestHeader设置referer响应头是有效的,这是因为浏览器为了平安起见,无奈手动设置局部保留字段,可怜的是Referer恰好就是保留字段之一,详情列表参考Forbidden header name。

利用fetch

// 将下载下来的二进制大对象数据转换成base64,而后展现在页面上function handleBlob(blob) {    let reader = new FileReader();    reader.onload = function(evt) {        let img = document.createElement('img');        img.src = evt.target.result;        document.getElementById('container').appendChild(img)    };    reader.readAsDataURL(blob);}const imgSrc = "https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg";function fetchImage(url) {    return fetch(url, {        headers: {            // "Referer": "", // 这里设置有效        },        method: "GET",          referrer: "", // 将referer置空        // referrerPolicy: 'no-referrer',     }).then(response => response.blob());}fetchImage(imgSrc).then(blob => {    handleBlob(blob);});

通过将配置参数referrer置空,能够看见本次申请曾经不带referer了

或者设置 referrerPolicy为"no-referrer"

服务器作防盗链图片直达

这里咱们应用express
index.js

const express = require('express');const app = express();app.use('/img', require('./routers/index.js'))app.listen(3000);

routers/index.js

var express = require('express');var router = express.Router();var request = require('request');router.get('/', function(req, res, next) {    var options = {        method: 'GET',        url: 'https://tiebapic.baidu.com/forum/w%3D580%3B/sign=f88eb0f2cf82b9013dadc33b43b6ab77/562c11dfa9ec8a135455cc35b203918fa1ecc09c.jpg',        headers: {            'Referer': '',        }    };    request(options).pipe(res)    });module.exports = router;

常见防盗链办法

防盗链个别有上面几种形式:

  1. 动静文件名,或者定期批改文件名称或门路
  2. 断定援用地址,个别是判断浏览器申请时HTTP头的Referer字段的值
  3. 应用登录验证,cookie
  4. 图片加水印
  5. ...

利用nginx

ngx_http_referer_module用于阻挡起源非法域名的申请 nginx指令valid_refers,全局变量$invalid_refer 对资源的防盗链nginx配置为

location ~.*\.(gif|jpg|png|bmp|flv|swf|rar|zip)${    valid_referers none blocked test.com *.test.com;   // 加none的目标是确保浏览器能够间接拜访资源    if($invalid_referer)    {        #return 403;  // 间接返回403        rewrite ^/ http://www.test.com/403.jpg; // 返回指定提醒图片    }}

这种办法是在server或者location段中退出:valid_referers。这个指令在referer头的根底上为 $invalid_referer 变量赋值,其值为0或1。如果valid_referers列表中没有Referer头的值, $invalid_referer将被设置为1。
如果 $invalid_referer等于 1,在if语句中返回一个 403 给用户,这样用户便会看到一个 403 的页面, 如果应用上面的rewrite,那么盗链的图片都会显示 403.jpg。
该指令反对none和blocked:

  • 其中none示意空的去路,也就是间接拜访,比方间接在浏览器关上一个文件
  • blocked示意被防火墙标记过的去路,*..com示意所有子域名

然而传统的防盗链也会存在一些问题,因为refer是能够伪造的, 所以能够应用加密签名的形式来解决这个问题。 什么是加密签名?就是当咱们申请一个图片的时候,我要给它带一些签名过来,而后返回图片的时候咱们判断下签名是否正确,相当于对一个暗号。
更多内容请参考 https://zhuanlan.zhihu.com/p/...

服务器端判断referer

咱们能通过比照req.headers['referer']和req.url中的host来确认资源申请是否是别的站点发来的。
接着,当咱们晓得了资源申请的起源,咱们就能通过一系列伎俩来决定是否响应申请以及怎么响应。
通常的做法是设置一个白名单,在白名单内的申请咱们就响应,否则就不响应。

let http = require("http");let fs = require("fs");let url = require("url");let path = require("path");// 白名单const whiteList = ["localhost:8080"];/** * 三种状况下容许援用图片: * 1. 本网站 * 2. 无referer信息的状况。(服务器认为是从浏览器间接拜访的图片URL,所以这种状况下能失常拜访) * 3. 受权的网址。(配置白名单) */http  .createServer(function (req, res) {    let refer = req.headers["referer"] || req.headers["refer"];    console.log('refer----', refer, req.url);    res.setHeader("Access-Control-Allow-Origin", "*");    if (refer) {      let referHostName = url.parse(refer, true).host;      let currentHostName = url.parse(req.url, true).host;      console.log(referHostName, currentHostName, '--==')      // 当referer不为空, 但host未能命中指标网站且不在白名单内时, 返回谬误的图      if (        referHostName != currentHostName &&        whiteList.indexOf(referHostName) == -1      ) {        res.setHeader("Content-Type", "image/jpeg");        fs.createReadStream(path.join(__dirname, "/src/img/403.jpg")).pipe(res);        return;      }    }    // 当referer为空时, 返回正确的图    res.setHeader("Content-Type", "image/jpeg");    fs.createReadStream(path.join(__dirname, "/src/img/1.jpg")).pipe(res);      })  .listen(9999);

利用http启动一个客户端:
client.js

let http = require("http");let fs = require("fs");let url = require("url");let path = require("path");// 创立服务器http.createServer(function (req, res) {  let staticPath = path.join(__dirname, "src");  let pathObj = url.parse(req.url, true);  if (pathObj.pathname === "/") {    pathObj.pathname += "index.html";  }  //  读取动态目录外面的文件,而后发送进来  let filePath = path.join(staticPath, pathObj.pathname);  fs.readFile(filePath, "binary", function (err, content) {    if (err) {      res.writeHead(404, "Not Found");      res.end("<h1>404 Not Found</h1>");    } else {      res.writeHead(200, "Not Found");      res.write(content, "binary");      res.end();    }  });}).listen(8080);

index.html

<div id="container">    <img src="http://localhost:9999"></div>

别离启动客户端和服务器:

如果咱们批改下服务器端whiteList:

// 白名单const whiteList = [];

重启服务器端,拜访客户端后 咱们发现响应后果变成了403图片:

避免网址被 iframe

在页面底部或其它专用部位退出如下代码:

// 用js办法检测地址栏域名是不是以后网站绑定的域名,如果不是,则跳转到绑定的域名上来,这样就不怕网站被他人iframe了if(window!=parent) {    window.top.location.href = window.location.href; }

注:
以上代码地址: https://github.com/zxl9257686...

参考资料:

https://developer.mozilla.org...

https://developer.mozilla.org...

https://juejin.cn/post/684490...

https://juejin.cn/post/684490...

https://www.cnblogs.com/wangy...