在实际开发的过程中,图片上传后的防盗是一个很大的问题,本文旨在介绍一种可行的思路
预签名
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