网易首页 > 网易号 > 正文 申请入驻

3步把S3图床变成"只认主"的保险箱,后端小白看完直接抄作业

0
分享至


去年有个做电商的朋友跟我吐槽:用户上传的头像存在MySQL里,数据库半年涨了800G,备份一次要通宵。我让他把图片迁到S3,只存引用,DB瞬间瘦回30G——这就是对象存储和关系型数据库的分工逻辑。

但迁上去只是第一步。真正让后端头疼的是:怎么防盗链?怎么让前端安全读取?怎么不让桶变成公共图床?

这篇指南从Node.js上传逻辑到IAM策略、CORS锁死,把每一层都拆开讲。适合已经会用AWS SDK、但还没搞懂权限体系怎么搭的开发者。

第一步:桶的创建和"假公开"陷阱

很多人创建S3桶时顺手点了"允许公共访问",或者以为Block Public Access全开就万事大吉——这两种都错。

正确的姿势是:用CLI创建时显式指定区域,然后关闭ACL级别的公共访问,但保留桶策略的口子。

代码如下:

aws s3api create-bucket \

--bucket your-app-images \

--region ap-south-1 \

--create-bucket-configuration LocationConstraint=ap-south-1

aws s3api put-public-access-block \

--bucket your-app-images \

--public-access-block-configuration \

"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false"

注意最后两个参数是false。我们要的不是"完全封闭",而是"只认规则的封闭"——接下来用桶策略精确控制谁可以读。

第二步:桶策略的"Referer白名单"机制

S3的权限体系有两层:IAM(谁可以操作桶)和桶策略(什么条件下允许访问)。这里我们专注后者。

核心思路是利用HTTP的Referer头。当浏览器从你的网站加载图片时,会自动带上你的域名;直接复制URL到地址栏,或者别的网站嵌套你的图,Referer就不匹配。

策略文件长这样:

"Version": "2012-10-17",

"Statement": [

"Sid": "AllowOnlyFromMyWebsite",

"Effect": "Allow",

"Principal": "*",

"Action": "s3:GetObject",

"Resource": "arn:aws:s3:::your-app-images/public/*",

"Condition": {

"StringLike": {

"aws:Referer": [

"https://yourwebsite.com/*",

"https://www.yourwebsite.com/*"

应用策略:

aws s3api put-bucket-policy \

--bucket your-app-images \

--policy file://s3-bucket-policy.json

这个方案只适用于"公开但防 hotlink"的场景,比如商品图、文章配图。如果是用户私有文件(身份证、合同),别用Referer策略,直接走预签名URL——下面会讲。

第三步:CORS配置和前端的"跨域焦虑"

桶策略管的是"能不能读",CORS管的是"浏览器让不让读"。如果你的前端用fetch或XMLHttpRequest直接请求S3,没配CORS会报经典错误:

Access to fetch at 'https://your-bucket.s3...' from origin 'https://yourwebsite.com' has been blocked by CORS policy.

配置CORS允许你的域名:

"AllowedHeaders": ["*"],

"AllowedMethods": ["GET", "HEAD"],

"AllowedOrigins": ["https://yourwebsite.com"],

"ExposeHeaders": [],

"MaxAgeSeconds": 3000

这里有个细节:AllowedOrigins不要写*,哪怕你暂时只有一个域名。未来加子域名或换主域时,你会感谢自己当初没偷懒。

第四步:Node.js上传逻辑——流式处理+元数据

前端把文件传给Node.js API,API再上传到S3。这个设计有两个好处:一是可以预处理(压缩、格式校验、病毒扫描),二是隐藏真实的S3桶名和路径结构。

用@aws-sdk/client-s3和@aws-sdk/lib-storage实现流式上传:

import { S3Client } from '@aws-sdk/client-s3';

import { Upload } from '@aws-sdk/lib-storage';

const s3Client = new S3Client({ region: 'ap-south-1' });

async function uploadImage(fileBuffer, fileName, mimeType) {

const s3Key = `public/${Date.now()}-${fileName}`;

const upload = new Upload({

client: s3Client,

params: {

Bucket: 'your-app-images',

Key: s3Key,

Body: fileBuffer,

ContentType: mimeType,

Metadata: {

'uploaded-by': 'user-id-123',

'original-name': fileName

const result = await upload.done();

return s3Key; // 只返回key,不返回URL

关键细节:Metadata字段可以存业务信息,比如上传者ID、原始文件名,方便后续审计和迁移。但别存敏感信息,S3元数据是明文的。

数据库只存s3Key,查询时动态生成URL。这样即使未来迁移到别的存储(Cloudflare R2、MinIO),只需改URL生成逻辑,不用改数据库。

第五步:预签名URL——私有文件的"临时通行证"

对于用户私有文件,桶策略那套Referer机制不够用:一旦URL泄露,任何人都能看。这时候需要预签名URL(Pre-signed URL)。

原理:用你账号的IAM凭证对请求进行签名,生成一个带过期时间的临时链接。S3验证签名有效且未过期,才返回文件。

实现:

import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

import { GetObjectCommand } from '@aws-sdk/client-s3';

async function getPrivateImageUrl(s3Key, expiresInSeconds = 300) {

const command = new GetObjectCommand({

Bucket: 'your-app-images',

Key: s3Key

const signedUrl = await getSignedUrl(s3Client, command, {

expiresIn: expiresInSeconds

return signedUrl;

过期时间建议:用户头像5分钟,合同文件30秒,下载链接10分钟。根据场景调,太短用户体验差,太长有泄露风险。

预签名URL的生成是计算密集型操作吗?不是。它只是本地做HMAC签名,不调用AWS API,可以放在高频接口里。

第六步:IAM最小权限原则

你的Node.js应用需要IAM凭证来操作S3。千万别用根账号密钥,要创建专用IAM用户,并绑定最小权限策略。

"Version": "2012-10-17",

"Statement": [

"Effect": "Allow",

"Action": [

"s3:PutObject",

"s3:GetObject",

"s3:DeleteObject"

],

"Resource": "arn:aws:s3:::your-app-images/*"

注意Resource末尾的/*。如果写成arn:aws:s3:::your-app-images,策略对桶内对象不生效,这是新手常见坑。

生产环境建议用IAM Role而不是长期凭证。如果部署在EC2或ECS,直接绑定Role;如果是Lambda,用执行角色。这样密钥不会出现在代码或环境变量里。

一个容易忽略的细节:Content-Type

上传时如果不指定Content-Type,S3会默认application/octet-stream。这会导致浏览器下载图片而不是直接展示,用户体验崩掉。

务必从上传请求中读取MIME类型,或者通过文件魔数(file-type库)检测,显式设置Content-Type。

另外,如果你用了CloudFront做CDN,记得配置缓存行为,让Content-Type参与缓存键。否则不同格式的同名文件会互相污染缓存。

这套架构跑通后,一个中等规模的UGC平台(日活50万,人均3张图)的存储成本大概在每月200-400美元区间,取决于访问频率和是否走CDN。相比自建MinIO集群加运维人力,这个账不难算。

你现在的图片存储方案是什么?有没有遇到过Referer被绕过、或者预签名URL过期时间调不准的坑?

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
兵者,诡道也?在伊朗“高风险搜救行动”的美版叙事

兵者,诡道也?在伊朗“高风险搜救行动”的美版叙事

澎湃新闻
2026-04-06 14:34:26
世界杯收官最新世界排名:松岛新高!覃予萱飙升11位,向鹏狂跌8位

世界杯收官最新世界排名:松岛新高!覃予萱飙升11位,向鹏狂跌8位

求球不落谛
2026-04-06 06:46:48
第4轮中超“最差”评选:上海海港1人,浙江队2人,津门虎2人

第4轮中超“最差”评选:上海海港1人,浙江队2人,津门虎2人

男足的小球童
2026-04-06 18:24:59
特朗普消失?连续三天未露面引全球猜测,病危还是兵变?真相成谜

特朗普消失?连续三天未露面引全球猜测,病危还是兵变?真相成谜

策略述
2026-04-06 18:12:08
军事 | 拯救大兵,美军炸了还是没炸?谁在说谎?

军事 | 拯救大兵,美军炸了还是没炸?谁在说谎?

新民周刊
2026-04-06 09:09:19
周迅姐姐的自律,全在这碗轻食里了。难怪一直这么瘦,状态绝了!

周迅姐姐的自律,全在这碗轻食里了。难怪一直这么瘦,状态绝了!

小椰的奶奶
2026-04-06 14:29:55
1998年数万华人遭屠杀,中国为何没出兵?26年后答案让人沉默

1998年数万华人遭屠杀,中国为何没出兵?26年后答案让人沉默

哄动一时啊
2026-02-17 22:21:25
突然拉升!美联储、鲍威尔,突发!降息,大消息!

突然拉升!美联储、鲍威尔,突发!降息,大消息!

证券时报e公司
2026-04-04 22:12:45
原来这才是普通家庭存款啊!网友:两套房一辆车,无房贷车贷

原来这才是普通家庭存款啊!网友:两套房一辆车,无房贷车贷

另子维爱读史
2026-03-06 20:12:51
王楚钦夺冠后一举动引发质疑,难怪樊振东、陈梦退出,现在懂了

王楚钦夺冠后一举动引发质疑,难怪樊振东、陈梦退出,现在懂了

林子说事
2026-04-06 18:22:45
所有人都以为大战接近尾声,伊朗突然掀起了开战以来最猛的导弹雨

所有人都以为大战接近尾声,伊朗突然掀起了开战以来最猛的导弹雨

老鹈爱说事
2026-04-07 03:41:25
扣2个日本人质后,伊朗人在日本被铁管打死,小日子飘得有点过了

扣2个日本人质后,伊朗人在日本被铁管打死,小日子飘得有点过了

温读史
2026-04-06 21:06:27
“张雪的机车”,被封禁

“张雪的机车”,被封禁

台州交通广播
2026-04-07 02:04:09
李镇全到底做了什么?让米特里策两次破防拽他头发,还因此被发现

李镇全到底做了什么?让米特里策两次破防拽他头发,还因此被发现

懂个球
2026-04-06 23:54:03
反对派对欧尔班釜底抽薪:上台后修改宪法,欧尔班无法再担任总理

反对派对欧尔班釜底抽薪:上台后修改宪法,欧尔班无法再担任总理

史行途
2026-04-07 03:42:49
张雪与凯越老总私下会面,对方主动祝贺,张雪顺势澄清辞职原因

张雪与凯越老总私下会面,对方主动祝贺,张雪顺势澄清辞职原因

潮鹿逐梦
2026-04-04 23:43:09
阿尔卡拉斯:辛纳赢下阳光双冠后还参加蒙特卡洛让我非常意外

阿尔卡拉斯:辛纳赢下阳光双冠后还参加蒙特卡洛让我非常意外

网球之家
2026-04-06 22:38:01
把情报局当私器、向俄罗斯泄密,一场匈牙利版水门事件如何收场呢

把情报局当私器、向俄罗斯泄密,一场匈牙利版水门事件如何收场呢

环球格局观
2026-04-05 21:57:54
解放军唯一的一次乌龙,一野和二野打了一个晚上,各自伤亡多少人

解放军唯一的一次乌龙,一野和二野打了一个晚上,各自伤亡多少人

旧史新谭
2026-04-05 17:00:22
研究显示:我国男性阴茎疲软状态下的均值是6.5厘米,你达标了吗

研究显示:我国男性阴茎疲软状态下的均值是6.5厘米,你达标了吗

黯泉
2026-04-05 19:00:47
2026-04-07 06:32:49
爬虫饲养员
爬虫饲养员
业余养了只叫“龙虾”的AI爬虫,主业是给互联网打工。
841文章数 7关注度
往期回顾 全部

科技要闻

折叠屏iPhone要来了,富士康已在试产!

头条要闻

特朗普:7日是最后期限 否则伊朗每座桥梁将被摧毁

头条要闻

特朗普:7日是最后期限 否则伊朗每座桥梁将被摧毁

体育要闻

官方:中国女足球员邵子钦加盟本菲卡

娱乐要闻

唐嫣罗晋新加坡遛娃,6岁女儿身高抢镜

财经要闻

史诗级暴跌"一周年" A股接下来如何走?

汽车要闻

阿维塔06T快上市了 旅行车还能这么玩?

态度原创

房产
家居
艺术
健康
军事航空

房产要闻

小阳春全面启动!现房,才是这波行情里最稳的上车票

家居要闻

温馨多元 爱的具象化

艺术要闻

这所小学的校牌竟然全由学生手写,已持续十年

干细胞抗衰4大误区,90%的人都中招

军事要闻

伊朗:在C-130运输机残骸中发现一具美军士兵遗体

无障碍浏览 进入关怀版