Nodejs 第三十章 express防盗链
什么是防盗链?
防盗链技术是用来阻止其他网站直接链接到我们网站的文件(尤其是图片和视频等媒体资源),防止我们网站的带宽被不正当使用。当其他网站试图通过直接链接到我们服务器上的文件来显示这些资源时,防盗链技术可以识别并阻止这种行为。
为什么需要防盗链?
- 节省带宽成本:如果不实施防盗链措施,任何人都可以在他们的网站上使用我们的媒体文件,导致我们的服务器承受额外的数据传输负担。这不仅会消耗服务器带宽,还可能增加我们的网络托管费用(不可承受之重)。
- 保护版权:防盗链技术有助于保护我们的内容版权,防止他人未经许可就使用我们网站的内容,尤其是图片、视频和其他专有文件。
- 提升网站性能:减少未授权的内容访问可以降低服务器的负载,从而提高网站的整体性能和响应速度。
- 提高安全性:防盗链还可以增强网站的安全性,阻止恶意网站利用我们的资源进行不当行为,例如展示不适宜的内容或嵌入恶意代码。
防盗链措施
通过HTTP引用检查:网站可以检查HTTP请求的来源,如果来源网址与合法的来源不匹配,就拒绝提供资源。这可以通过服务器配置文件或特定的脚本实现。
使用Referrer检查:网站可以检查HTTP请求中的Referrer字段,该字段指示了请求资源的来源页面。如果Referrer字段不符合预期,就拒绝提供资源。这种方法可以在服务器配置文件或脚本中实现。
使用访问控制列表(ACL) :网站管理员可以配置服务器的访问控制列表,只允许特定的域名或IP地址访问资源,其他来源的请求将被拒绝。
使用防盗链插件或脚本:一些网站平台和内容管理系统提供了专门的插件或脚本来防止盗链。这些工具可以根据需要配置,阻止来自未经授权的网站的盗链请求。
使用水印技术:在图片或视频上添加水印可以帮助识别盗链行为,并提醒用户资源的来源。通过我们前面有讲到的ffmpeg就可以实现
防盗链的应用
-
好,通过上面的初步了解,我想你对于防盗链的整体已经有一个初步的理解了,这对我们进行学习非常有帮助。
- 防盗链在各种平台上其实都是普遍存在的,比如说CSDN和掘金的图片都是自带水印的
- 就算是我们在
配置图床
的时候,也可以选择性的开放白名单(允许访问图床链接的名单),或者把某些网站拉入黑名单。这也是防盗链
的一种体现
-
而我们刚刚用于初步了解的知识也可以马上在各种配置防盗链的网站中体现出来
- 如以下最常用的做法:
代码实现
-
通过防盗链的应用,我们也知晓了普遍的做法是怎么样的了,现在就让我们来动手实践一下吧!
- 既然上一章节已经学过express了,我们这次就不使用原生的http模块来实现,而是采用express框架进行
初始化项目
- 初始化package.json文件:npm init -y
- 安装express:npm i express
- 创建static文件夹:放我们的静态资源,等下来模拟防盗链效果的素材
- 创建app.js文件:写源码的地方
express.static方法
-
这里我们用到了express.static这个方法,虽然通过使用我们也能大概猜测出他的作用,但这是主观的做法,我们进行使用的时候,一定要阅读一下文档
- 这里就单独抽离出static这个方法,而其他有用到的时候我们再进行讲解
express.static
是 Express.js 中一个非常有用的内置中间件函数,用于提供静态文件(如 HTML 文件、图像、JavaScript 文件和 CSS 文件)。这个中间件函数可以非常方便地将某个目录下的文件作为静态资源暴露出来。
使用方法
基本使用
js
const express = require('express');
const app = express();
// 提供 public 目录下的静态文件,不需要添加前缀/
app.use(express.static('public'));
app.listen(3000, () => {
console.log('运行服务 http://localhost:3000');
});
- 在这个例子中,
express.static('public')
创建了一个中间件,将public
目录下的所有文件作为静态资源提供。如果我们在public
目录下有一个名为image.png
的文件,就可以通过http://localhost:3000/image.png
访问到
自定义虚拟路径前缀
php
// 只需在 URL 中添加 /static 前缀即可访问 public 目录中的文件
app.use('/static', express.static('public'));
这里,静态文件服务被设置在了一个虚拟的路径/static
之下。这意味着,如果我们有一个 styles.css
文件在 public
目录下,它将通过 URL http://localhost:3000/static/styles.css
访问。
提供多个目录下的静态文件
js
app.use(express.static('public'));
app.use(express.static('files'));
Express 会按照我们调用 app.use()
的顺序查找文件。首先会在 public
目录下查找,如果没有找到,再在 files
目录下查找
设置选项
js
// 设置静态文件服务的选项
app.use('/static', express.static('public', {
index: false, // 禁用目录索引
immutable: true, // 提供的文件都是不可变的,在缓存控制中设置 maxAge 会使用 immutable 属性
cacheControl: true, // 启用 Cache-Control HTTP 头
maxAge: '30d' // 指定从浏览器缓存中提供资源的最长时间
}));
- 这样的使用方法,客观的说,确实比直接使用
fs模块
来得更高效一些,但作为构建这些方便的使用方式基石的fs模块
也是必须掌握的知识点,能够帮助我们更高效的去理解这些
访问图片
-
我们往
static文件夹
内塞点图片,先从能正常访问图片 再到往图片添加防盗链,逐步进行,更好接受- 我们是将静态图片资源以
"中间件插入形式"
进行使用的,其中我们还是添加了一个前缀,在之前的章节中也有说明这个的作用是用来防止重名和让URL结构更加清晰的(看地址的时候也更好理解) - 之所以这么解释,是因为
use
是一个用于挂载中间件函数到指定的路径的方法。并且如果我们没有提供路径的话,默认是'/'
,这意味着应用的每个请求都会执行这个中间件。这可就不妙了,这相当于挂载全局中间件 ,跟把我们所有的接口内容写在一个app.js文件
里面是一个做法了 (不建议) - 而我们模块化的思想在前面章节已经有所体现,优秀的编程思想应该得到贯彻。这里做出提供前缀路径也正是这种思想魅力的体现
- 我们是将静态图片资源以
js
import express from "express"
const app = express()
app.use('/picture', express.static('static'))
app.listen(3000, () => {
console.log("3000端口服务已经启动");
})
防盗链的实现
-
在做到显示图片之后,我们就可以进行防盗链的实现了。
- 在进行代码编写的时候,我们首先需要有点思路,我们要怎么去实现?
- 在通过前面章节自己编写中间件实现日志展示,我们的思路也得到了开阔。
- 我想防盗链也是应该调用某个第三方库来设置或者自己手写,然后以中间件的形式挂载到我们接口前面。为什么说是前面? 因为我们的中间件调用他是有顺序的,先定义的中间件函数先执行(先来后到原则)。在之前章节我们叫这种定义顺序
倒反天罡
的行为叫做马后炮,没啥作用
-
OK,我们有了初步的想法后,来看下是不是这样实现的!
js
// 防盗链实现
// 白名单
const whiteList = ['localhost']
const preventTheftLink = (req, res, next) => {
const referer = req.get('referer')
if (referer) {
const hostname = new URL(referer)
// 不在白名单内进行403网络状态码处理
if (!whiteList.includes(hostname)) {
res.status(403).send('这是一个防盗链~禁止访问')
return
}
}
next()
}
app.use(preventTheftLink)
-
现在我们需要解释一下一些注意点:
req.get('referer')
这段内容是很好理解的,获取referer(引用页) 嘛,在我们初步了解的时候,就知道可以通过这个来进行判断当前网站是否处于白名单。但如果我们直接在浏览器访问图片的话,是获取不到这个referer
信息的。这意味着我们就无法对这个做出限制- 所以说我上图举例乃是采用挂载一个静态网站,然后在这个网站中引入防盗链图片的形式进行的
- 但其实换个角度来说,我们也没必要对这种方式做出限制。毕竟我们的目的是防止其他网站盗用我们的资源,而在浏览器中打开图片是一个很正常的行为。就算是掘金等其他防盗链的网站,也都是可以直接在浏览器访问图片的
模块化拆分(中间件)
-
在我们一开始的思路中,这应该是一个中间件,如果说我们就这样直接干脆写在
app.js
文件的话,可能说就会变得臃肿。- 现在就让我们来拆分一下吧!
- 首先创建
middleware文件夹
,在这个文件夹中创建preventTheftLink.js
文件作为我们编写防盗链中间件的地方。然后把我们刚才的内容搬迁进去,这里需要注意一个点,我们刚刚白名单是放在函数外面的,这时候就不可以了,我们要放在函数内部形成一个整体,才能避免访问不到的情况
preventTheftLink.js防盗链文件
js
// 防盗链实现
const preventTheftLink = (req, res, next) => {
// 白名单
const whiteList = ['localhost']
// 防盗链关键
const referer = req.get('referer')
if (referer) {
const hostname = new URL(referer)
// 不在白名单内进行403网络状态码处理
if (!whiteList.includes(hostname)) {
res.status(403).send('这是一个防盗链~禁止访问')
return
}
}
next()
}
export default preventTheftLink
app.js文件
-
此时,把防盗链的逻辑拆分了出去,我们的
app.js主文件
就会变得清爽了起来- 在上一章节中学到的,这就可以运用上了
js
import express from "express"
import preventTheftLink from "./middleware/preventTheftLink.js"
const app = express()
app.use(preventTheftLink)
app.use('/picture', express.static('static'))
app.listen(3000, () => {
console.log("3000端口服务已经启动");
})
Referer字段科普
- 在我们实现防盗链的时候,其实代码的本身是很简单的。也就是自己设置一个白名单,然后和Referer字段进行对比,如果不在白名单内,就返回响应状态码403(我收到了请求,但我拒绝返回响应)。
- 这里面最关键的其实是Referer字段,我觉得我们在写完之后,为了减少不必要的困惑,有必要去了解一下这个字段的由来,未知是阻碍学习最大的敌人,因为未知会带来恐惧和害怕,让我们下意识去抵触,那就永远无法掌握。
什么是 Referer?
在 Web 开发中,HTTP Referer
(通常发音为"referrer")是一个 HTTP 头部字段,用于指示当前请求的网页的来源网页的地址。这个字段的主要作用 是让服务器知道用户是从哪个页面链接跳转过来的
。
来源和拼写
Referer
头部是在 HTTP 协议早期定义的,具体可以追溯到 1990 年代初期的 RFC 1945 文档。有趣的是,Referer
的拼写实际上是一个历史性的拼写错误。正确的英文拼写应该是 "Referrer",但当时的文档作者在 RFC 文档中误写成了 "Referer",自此这个错误就被保留在了技术标准中。
作用
- 网站分析 :
Referer
头部是网站分析工具中非常重要的一部分,因为它可以帮助网站所有者理解他们的流量来源。例如,网站管理员可以看到哪些外部链接带来了最多的访问者。 - 防盗链 :
Referer
头部也常用于所谓的"防盗链"技术,也就是我们刚实现的方式。网站可以检查Referer
头部,以确保请求的内容(如图片或视频)是从自己的网站页面发起的,而不是被其他网站盗用。 - 内容定制 :某些网站使用
Referer
信息来提供更加定制化的内容,例如,基于用户之前访问的页面推荐相关内容或广告。 - 安全措施 :在一些安全敏感的应用中,
Referer
头部可以用来实施基于来源的安全策略,例如限制或允许某些来源的请求访问特定资源。
安全和隐私考量
尽管 Referer
头部有其用途,但它也涉及到一些隐私问题,因为它可能无意中泄露用户的浏览习惯或其他个人信息。为了解决这个问题,开发者可以使用 Referrer-Policy
HTTP 头部来控制哪些信息可以被包含在 Referer
中,或完全禁止发送 Referer
。