在实际开发的过程中,图片上传后的防盗是一个很大的问题,本文旨在介绍一种可行的思路

预签名

Minio 是支持预签名的,通常的桶上传需要在上传的时候就对文件进行预签名,但我们客户端上传是统一封装成一个 API 的,前端并不实际和桶进行交互,因此这里在上传并不需要进行预签名

真正需要进行预签名的是对读取桶文件的 URL 进行预签名

我们在上传逻辑的实现中,创建了两个用户组,一个是具有写权限的 writer,另外一个是只有读权限的 reader,我们将两者权限分开,在后端使用 writer 进行写操作,返回桶名和文件名,之后使用 reader 生成预签名的 URL

预签名在 node.js 中的实现为:

/ 创建 guest 用户的 MinIO 客户端用于生成预签名 URL
const guestMinioClient = new Minio.Client({
    endPoint: process.env.MINIO_ENDPOINT,
    port: parseInt(process.env.MINIO_PORT, 10),
    useSSL: process.env.MINIO_USE_SSL === 'false',
    accessKey: process.env.MINIO_GUEST_ACCESS_KEY,  
    secretKey: process.env.MINIO_GUEST_SECRET_KEY   
});
    
// 使用 guest 客户端生成预签名 URL
const url = await guestMinioClient.presignedGetObject(
    bucket, 
    objectName, 
    60 * 60 * 24 * 7  // 7天过期时间,单位秒
);

在首次写入完成后,后端会将桶名、文件名、预签名和过期时间存入数据库,我们使用的是 mongodb

之后前端通过统一的 API 接口进行图片获取,后端会先检查预签名是否过期,如果没有过期则直接返回给前端,如果过期了则重新进行签名,返回给前端并且重新存入到数据库中

有什么弊端

数据库的压力会变大

原先只需要存入一个简单的图链,现在却需要存储名、文件名、预签名和过期时间,而且当用户访问一些比较旧的数据,需要多次预签名,数据库和 minio 的压力都会变大

预签名 URL 可被复制传播

预签名的 URL 并不是真正意义上的「私有访问」,当用户拿到链接之后依然可以分享

  • 将 URL 进行限制,比如说 Referer、User-Agent

预签名 URL 生成是同步调用,有一定性能损耗

每次访问旧的图片资源都需要重新调用 presignedGetObject,这是一个阻塞调用,可能会造成一定的损耗,而且这个操作涉及认证,可能对服务器造成较大压力

引入 Redis

Redis 中存储的结构

Key: img:avatar:abc.jpg
Value:
{
  "url": "https://minio.xxx.com/abc.jpg?X-Amz-Signature=...",
  "expiresAt": 1723012345
}
TTL: 6 days (假如 URL 有效期是 7 天)

整体的服务框架

客户端请求图片信息
       │
       ▼
检查 Redis 是否存在有效签名 URL
       │
 ┌─────┴─────┐
 │           │
是           否
│            │
返回 URL     查询 MongoDB → 使用 reader 签名 → 写 Redis → 返回 URL
最后修改:2025 年 05 月 04 日
如果觉得我的文章对你有用,请随意赞赏