Referer和Referrer Policy以及图片防盗链

news2024/11/20 18:45:57

Referer

Referer请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用Referer(注:正确英语拼写应该是referrer,由于早期HTTP规范的拼写错误,为了保持向后兼容就一直延续下来)请求头识别访问来源,可能会以此统计分析、日志记录以及缓存优化等。

注: Referer请求头可能会暴露用户的浏览历史、涉及到用户的隐私问题。

Referrer-policy

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

包含以下值:

  • no-referrer : 整个referee首部会被移除,访问来源信息不随着请求一起发送。
  • no-referrer-when-downgrade : 在没有指定任何策略的情况下用户代理的默认行为。在同等安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送 (HTTPS->HTTP).
  • origin: 在任何情况下,仅发送文件的源作为引用地址。例如 example.com/page.html 会…
  • 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(移除参数信息之后)作为引用地址。(最不安全了)

浏览器兼容性(caniuse.com/?search=ref…%25EF%25BC%259A “https://caniuse.com/?search=referer-policy)%EF%BC%9A”)

如何设置referer

  1. 在HTML里设置meta
<meta name="referrer" content="origin"> 

如下图:

  1. 或者用、、、、

如:

<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生成自签名证书(具体可看github.com/zxl92576866…
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, "OK");
      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”

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

[images.weserv.nl/?url=`KaTeX parse error: Expected '}', got 'EOF' at end of input: …erv.nl/?url=%60%7B%E4%BD%A0%E7%9A%84%E5%9B%BE%E7%89%87%E5%9C%B0%E5%9D%80%7D%60")

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

利用iframe伪造请求referer

内容参考 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头部

内容参考 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. 图片加水印

利用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是可以伪造的, 所以可以使用加密签名的方式来解决这个问题。 什么是加密签名?就是当我们请求一个图片的时候,我要给它带一些签名过去,然后返回图片的时候我们判断下签名是否正确,相当于对一个暗号。
更多内容请参考 zhuanlan.zhihu.com/p/362650878

服务器端判断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, "OK");
      res.write(content, "binary");
      res.end();
    }
  });
}).listen(8080); 

index.html

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

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

如果我们修改下服务器端whiteList:

// 白名单
const whiteList = []; 

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

403.png

防止网址被 iframe

在页面底部或其它公用部位加入如下代码:

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

注:
以上代码地址已提交GitHub: github.com/zxl92576866…

参考资料:

developer.mozilla.org/zh-CN/docs/…

developer.mozilla.org/zh-CN/docs/…

juejin.cn/post/684490…

juejin.cn/post/684490…

www.cnblogs.com/wangyongson…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html 简单表格制作(看了它足以应对大部分表格)

目录 基础表格 进阶表格 锦上添花表格 bgcolor background align frame元素 基础表格 首先制作一个表格我们要知道一张简单表格就是由二部分组成分别是表头&#xff0c;表身。 下面就是一个简单的表格。 表头就是黑体加粗的内容&#xff0c;表身就是表格主要表达的内容。…

css里面设置按钮(button)让字体居中

题目&#xff1a;设置button中的字体让其居中&#xff0c;不至于溢出(字体下落&#xff0c;重影等问题) 1&#xff0c;抛出问题&#xff0c;如图所示 2&#xff0c;引出我的代码 <view class"loginBtn"><form action"check.jsp" method"get…

带你吃透Servlet技术(二)

个人主页&#xff1a; 几分醉意的CSDN博客_传送门 前言&#xff1a;在上一篇&#xff0c;我们已经初步的了解了 Servlet技术 传送门&#xff0c;接下来我们继续深入学习Servlet。 本文目录&#x1f496;继承HttpServlet实现Servlet程序✨代码实战✨自动生成doGet和doPost方法✨…

猿创征文|如何使用 Element UI? 以登录框为例带你感受一下基础使用

目录 前言 一、安装&#xff08;所有内容&#xff09; 二、按需引入 三、案例演示 1.案前整理 2.代码演示&#xff08;后附源码&#xff09; 3.源码 前言 Element-ui&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库&#xff0c;提供了配套设计…

el-upload上传文件

el-upload上传文件 前言 公司和学校项目都用到了上传文件的功能&#xff0c;记录一下。 准备 express实现的上传接口 const express require(express); ​ // 文件上传模块 const multiparty require(multiparty) ​ // 提供跨域资源请求 const cors require(cors) ​ …

Idea中使用Tomcat部署并启动Web项目

首先在Idea中选择编辑运行配置&#xff0c;如下图 左上角的“”号&#xff0c;选择Tomcat服务&#xff0c;如下图 自定义服务名称和项目在浏览器的访问路径 配置Tomcat服务器路径&#xff0c;如下图 然后在服务器中部署项目&#xff08;下面的警告提示&#xff1a;Warning: No …

nginx响应码301及访问路径参数丢失之间的关系

nginx响应码301及访问路径参数丢失之间的关系 本文比较长&#xff0c;所以写了一篇比较短的结果导向的文章&#xff0c;换了一下思路&#xff0c;大家可以看一这篇文章&#xff0c;如果感兴趣再来看这篇文章&#xff1a;nginx导致vue设置history模式下的请求丢失参数 背景描述…

Java使用WebStocket实现前后端互发消息

记录一下自己使用WebStocket实现服务器主动发消息的过程和踩得雷。 需求&#xff1a;车牌识别系统识别到车牌后&#xff0c;持续向前端推送车牌信息&#xff0c;直到前端回复收到。 测试需求&#xff1a;新增 客户后&#xff0c;持续向前端推送客户信息&#xff0c;直到前端收…

type=“module“ 你了解,但 type=“importmap“ 你知道吗

新出了一个系列&#xff1a;Vue2与Vue3 技巧小册 有梦想&#xff0c;有干货&#xff0c;微信搜索 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。 本文 GitHub https://github.com/qq449245884/xiaozhi 已收录&#xff0c;有一线大厂面试完整考点、资料以及我的系列文章。 当E…

css的rotate3d实现炫酷的圆环转动动画

1.实现效果 2.实现原理 2.1 rotate3d rotate3d&#xff1a;rotate3d() CSS 函数定义一个变换&#xff0c;它将元素围绕固定轴移动而不使其变形。运动量由指定的角度定义; 如果为正&#xff0c;运动将为顺时针&#xff0c;如果为负&#xff0c;则为逆时针。 语法&#xff1a; …

【面试题】redux及中间件相关面试题解析

1、什么是Redux&#xff1f; Redux就是一个js容器&#xff0c;用于全局的状态管理 2、为什么在React项目中要使用Redux&#xff1f; 因为React本质上就是一个UI库&#xff0c;它是单向数据流的&#xff0c;就是说数据只能从父组件通过props流向子组件&#xff0c;但如果子组…

Vue 无感刷新token

关于无感刷新的理解: 实现token无感刷新对于前端来说是一项非常常用的技术,其本质是为了优化用户体验,当token过期时不需要用户跳回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的请求,获取最新的token进行覆盖,让用户感受不到token已经过期 刷新token的一些方案…

零基础JavaScript学习【第六期】

博主有话说:这个礼拜有些忙,离上一次更新已经过了亿天,这几天较忙所以更新会有点慢,谢谢大家的支持。(っ•̀ω•́)っ✎⁾⁾ 我爱学习 个人空间:GUIDM的个人空间 专栏内容:零基础JavaScript学习 基础还是关键。 欢迎大家的一键三连。 前情回顾:第一期https://blog.cs…

uni-app - 最详细 H5 网页接入微信支付功能,提供从详细的示例代码与注释(移动端网页实现微信支付能力,微信公众号前端支付 JSAPI / JS-SDK 详细示例源码)官方最新超级详细教程

前言 关于 uni-app 项目中接入微信支付的文章鱼龙混杂,各种 JSAPI / JS-SDK 乱代码、过时、没注释、不讲流程原理,非常难用。 本文实现了 uni-app H5 移动端网页项目,实现微信支付功能,详细讲解接入流程及超详细示例代码, 从 0-1 完全站在小白的角度,让您无需太多知识也…

【React】React入门--生命周期

&#x1f380;个人主页&#xff1a;努力学习前端知识的小羊 感谢你们的支持&#xff1a;收藏&#x1f384; 点赞&#x1f36c; 加关注&#x1fa90; 文章目录React生命周期初始化阶段运行中阶段销毁阶段老生命周期的问题新生命周期代替性能优化的方案重要的勾子即将废弃的勾子R…

【ElementUI样式优化1】el-table 修改斑马格样式、修改滚动条样式、添加表头边框、删除表格边框划线

重要的不是过去&#xff0c;而是你怎末看待过去&#xff0c;而我们对过去的看法&#xff0c;是可以改变的。 效果预览&#xff1a; &#xff08;1&#xff09;删除表格外框&#xff0c;内框。 &#xff08;2&#xff09;添加表头边框&#xff0c;修改表头文字大小、颜色 &…

猿创征文 |【高级篇】Java 进阶之JVM实战

文章目录⚡前言一、面试题解析二、JVM 理论详解⛅JVM的位置❄️JVM的体系结构⏳类加载器三、JVM 双亲委派机制四、Native 关键字五、PC寄存器 与 方法区六、栈与堆七、三种JVM、新生区、老年区、永久区⛵小结⚡前言 JVM 是 Java 实现 跨平台的基础&#xff0c;所有的Java 程序…

[遇到的问题-已解决]Cannot resolve plugin org.apache.maven.plugins:maven-compiler-plugin:3.1

如上图所示&#xff0c;这是我解决好的&#xff0c;刚开始的时候爆红有这些&#xff1a; 我按照在网上查找的方法&#xff0c;一一试了。 首先&#xff0c;maven 安装的路径和和本地仓库的目录必须要保持一致 打开setting-Build,Excution,Deployment-Build Tools-Maven&#…

vue框架介绍

概述 Vue 是一套用于构建用户界面的渐进式框架 对渐进式的理解&#xff1a; 每个框架都不可避免会有自己的一些特点&#xff0c;从而会对使用者有一定的要求&#xff0c;这些要求有强有弱&#xff0c;它的强势程度会影响在业务开发中的使用方式。 vue的定位&#xff1a; 我在做…

【Vue入门必备知识篇03】--- 生命周期 数据共享

前言❤️ 过好自律的生活&#xff0c;美好才会在路上不期而遇 ❤️【Vue入门必备知识篇03】--- 生命周期 & 数据共享一、生命周期 & 数据共享&#xff08;1&#xff09;组件的生命周期1.1 生命周期 & 生命周期函数1.2 组件生命周期函数的分类1.3 生命周期图示&…