1. 一张 S3 图片链接,为什么经常在“公开”这一步翻车
先把这个需求拆开看:往 Amazon S3 上传一张图,让这张图能通过链接被任何人直接打开,看起来只是个发布操作,其实背后同时牵扯到“上传”“公开访问配置”“Content-Type 元数据”三件事。我经常看到一些同行兴冲冲地把图片传上去,复制了对象 URL 发给别人,结果对面点开是一段 XML,上面写着 AccessDenied。这不是网速问题,也不是 AWS 抽风,而是默认状态下 S3 几乎把所有“所有人都能读”的通道都堵死了。
AWS 把“公开”做成了默认要主动申请的状态,原因不难理解:企业或个人常把 S3 当私有文件库,如果一传上去就全世界可读,那每次上传身份证扫描件简直是灾难。但从新手的角度讲,去控制台找“公开”开关时,能明显感觉到有些入口很隐蔽,有些设置相互牵连,比如桶策略、对象 ACL、Block Public Access 这些概念混在一次,容易让人抓瞎。
这篇文章我打算完整走一遍从创建桶到直链可访问的流程,包含 AWS 管理控制台操作和命令行 CLI 操作两套方式。图片上传后能直接被浏览器打开而不是被强制下载,是这里要重点解决的问题。只要按链路排查,二十分钟内基本能结束战斗。
1.1 先看懂 S3 的“公开链路”到底由哪些开关控制
S3 里一个对象要想被匿名访问,大致要满足下面这串条件:
- 存储桶的“阻止公共访问(Block Public Access)”不能被全部打开,或者说至少得允许公开策略生效。
- 存储桶策略或者对象 ACL 中,必须有一条允许
s3:GetObject的规则,Principal 要包含匿名用户。 - 存储桶所在账号如果启用了账号级别的 Block Public Access,同样会覆盖桶上的设置。
- 对象本身不能被加密密钥拦截,否则即使权限允许,浏览器也会拿不到数据。用 S3 默认的 SSE-S3 加密通常没有这个问题。
很多人只修改了其中某一步,自然打不开。比如桶策略写得没问题,但 Block Public Access 没关,那么访问时会直接被系统拒绝。
另外一个容易被忽略的点是“能读文件”和“能在浏览器里显示”并不是一回事。 S3 在返回图片时,会带上一个 Content-Type 响应头,比如 image/jpeg,浏览器看到这个头才会把内容当成图片渲染出来。如果上传时没有正确写入这个元数据,或者 Content-Disposition 被设置成了附件,那么即使用户权限正常,浏览器也可能下载到本地而不是直接打开。这就是为什么有些人的图片是“Access Denied”,有些人的图片是“直接下载”。
1.2 最终目标状态:公有读、私有写
关于 Public 公开,我们要达到的并不是把整个桶的可见性完全暴露出去。更推荐的状态是:匿名用户能读取指定前缀下的对象,但只有你或者授权的 IAM 用户能写入、删除、改配置。
这种模式可以概括为“公有读、私有写”。标题里的 Public 指的是 Read,并不是对存储桶管理操作开放权限。如果你在写桶策略时只给了 s3:GetObject,而没有开放 s3:ListBucket,那别人就没法通过接口遍历你的文件名;他必须知道某个具体的对象 URL 才能访问。这样做既满足了图片直链需求,又不至于让整个目录结构裸奔,安全等级会高很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前把这几件事先定下来
2.1 存储桶命名和区域选择
S3 存储桶名字是全局唯一的,也就是说全 AWS 的客户共用同一个命名空间,不能叫 my-image-bucket 这种太常见的名字,大概率会被提示重名。我习惯在名字后面加上业务缩写和日期,比如 myteam-image-backup-2025,看着不优雅但很实用,也方便审计。
区域选择上,通常建议选离你的访问用户更近的区域。如果主要给同事或内部系统用,选你当前 VPC 相近的区域即可。如果给外部网站引用,建议考虑访问延迟和流量成本后再定,不必盲目选择热门区域。同区域间的 AWS 服务传输数据一般不会额外收流量费,跨区域则麻烦一些。
2.2 目录结构决定了后续策略好不好写
新建桶时不用急着创建文件夹,但上传前要定好目录规划。我个人的习惯是:需要公开的资源统一放进 public/ 前缀下,内部私有文件放到 private/ 或直接放其他桶。
这样做最大的好处是,桶策略可以直接写成只对 public/* 生效。以后即使有人误操作,把某些本该私有的对象传到根目录,也不会因为一条通配策略而被公开。权限配置中“最小粒度”这件事不能只靠人肉把关,尽量让规则和目录结构配合起来锁死边界。
2.3 选择控制台还是 CLI
如果只是偶尔传几张图,直接在 AWS 管理控制台点点点就够了。如果后续要接 CI/CD、要写脚本批量上传,就必须用 AWS CLI 或 SDK。我会在第 4 节单独把 CLI 流程列出来,因为它不需要反复打开网页,对生产环境更有参考价值。
3. 控制台完整操作:建桶、放开公开读、传图拿直链
3.1 建桶时先把“阻止所有公共访问”这一项关掉
登录 AWS 管理控制台,进入 S3 服务页面,点击“创建存储桶”。填完桶名和区域后,在“对象所有权”这一项通常会看到默认值“ACL 禁用(Bucket owner enforced)”。这里继续保留默认值就行,因为我们后面的公开访问方案走的是桶策略,不是对象 ACL。
许多人会在这里纠结要不要把 ACL 启用,其实没必要。ACL 适合比较老的权限模型,新项目统一用 IAM 和 Bucket Policy 更清晰。如果某些场景下确实需要单独给某个对象设置公共读,再考虑开启 ACL。
接下来是关键的“阻止公有访问(Block Public Access settings for this bucket)”。默认是全部勾选状态,点击“编辑”,把“阻止所有公共访问”前面的勾去掉。如果你不关这一步,哪怕后面桶策略写得再完整,系统也会以 Block Public Access 为准,直接忽略公开授权。
确认后点击创建桶。这时候 S3 控制台可能会在列表中给这个桶打上“公共访问”相关的提示,这并不表示桶已经公开,只是说明它具备了设置公开策略的条件。
3.2 给桶写策略:只对某个公共目录开放读取
进入创建好的存储桶,选择“权限”标签页,往下找到“存储桶策略”,点击“编辑”。在策略编辑框里粘贴下面的 JSON:
json复制{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadOnlyImages",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::你的桶名/public/*"
}
]
}
这里有几个字段需要仔细说明:
Principal是"*",代表所有匿名用户和未登录用户,这是 Public 的关键。Action是s3:GetObject,也就是读取对象内容。Resource需要写成对象 ARN。对象 ARN 的格式是arn:aws:s3:::桶名/对象完整key。因为 key 是动态的,所以通常用通配符/*代表整个桶内的所有对象,或者像上面这样用/public/*代表某个前缀下的所有对象。只写arn:aws:s3:::桶名是不行的,因为那样只能代表桶级权限,对象读取权限拿不到。
我建议先用 public/* 这样限定范围。如果你只有几张小图且不介意整个桶都公开,也可以把 Resource 写成 arn:aws:s3:::你的桶名/*,那意味着这个桶下的每个对象,只要被传上来,就都会被任何人直接读取。对生产桶来说风险太高,建议只在测试环境这样做。
保存策略后,S3 可能会提示“该存储桶具有公共访问权限”,这是正常现象,因为策略允许公网读取。如果你看到的不是这个提示,而是类似“此存储桶已阻止公共访问”的警告,那多半是 3.1 里的 Block Public Access 没有彻底关掉,或者账号级别开了限制。
3.3 上传图片并复制可访问 URL
上传前先确认一下你的访问计划。如果策略限制的是 public/*,那图片的 key 必须从 public/ 开头。操作上可以先创建一个 public 文件夹,再进入文件夹里拖拽上传,或者直接在上传时把目标路径写成带 public/ 前缀的位置。
进入桶后,点击“上传”。把本地图片拖进上传区域后,可以展开“属性”区,确认一下内容类型,比如 image/jpeg。AWS 控制台上传文件时通常会根据扩展名自动填好 Content-Type,但如果用的是一些比较冷门的方式上传,后面要再手动检查。
上传完成后,在对象列表里找到刚才的图片,点击图片名称进入详情页,在顶部会看到一个“对象 URL”。点旁边的“复制 URL”按钮,把链接拿出来:
text复制https://你的桶名.s3.你的区域.amazonaws.com/public/你的图片.jpg
把链接粘贴到浏览器的隐身窗口,如果一切正常,浏览器会直接显示图片。我用隐身窗口测,就是为了排除本地浏览器缓存和账号登录态的干扰。
如果返回 403 Access Denied,按优先级查三件事:一是 Block Public Access 是否关闭,二是桶策略里的 Resource 是否匹配 key,例如你传到了根目录但策略只允许 public/*,自然打不开,三是账号级阻止公开访问是否覆盖了桶级配置。如果返回的是 NoSuchKey,则多半是链接里的 key 写错了,检查大小写和路径。
3.4 想让某一张图片单独公开,怎么处理
有人会问,我不想把整个目录开放,就希望某一两张图片公开,能不能用对象 ACL 解决。可以,但操作前提是桶的“对象所有权”设置成允许 ACL,也就是我们不能用默认的 Bucket owner enforced,得在创建桶或者编辑桶配置时打开 ACL 启用。
在控制台上传对象后,选中对象,在“权限”标签页里,把“公共读取”权限勾上。这实际上会给对象写入一条 ACL,允许匿名用户读取。要注意的是,AWS 官方趋势更推荐用桶策略 + 路径前缀,因为你每传一个新对象,对象 ACL 不会自动继承,搞不好哪天漏设置就多出一个本不该公开的文件。我的经验是:能给前缀的就给前缀,能不用 ACL 就不用手动 ACL。
4. 用 AWS CLI 来一套自动化流程
4.1 安装和配置 AWS CLI
如果你机器上已经有 AWS CLI,先执行 aws --version 确认版本。新版 CLI 2 的安装包在多数系统上都能一键完成,macOS、Linux、Windows 都有相应安装方式。没有安装的话,直接去 AWS 官方文档找安装包,过程很直白,不用绕路。
配置凭证时先要去 IAM 创建一个用户,给这个用户至少挂上 AmazonS3FullAccess 权限。生产环境请不要用根账号的 Access Key,养成写 IAM 用户的最小权限习惯。创建完成后,在终端执行:
bash复制aws configure
按提示输入 Access Key ID、Secret Access Key、区域,例如 ap-northeast-1,输出格式可以用 json。
4.2 创建桶并关闭公共访问拦截
假设存储桶名是 myexample-public-images-2025,区域在东京,也就是 ap-northeast-1,先创建桶:
bash复制aws s3api create-bucket \
--bucket myexample-public-images-2025 \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
如果你用的区域是 us-east-1,那么创建命令里可以省略 --create-bucket-configuration 参数。这个参数是个区域约束,非 us-east-1 区域创建桶时基本都要带。
新建的桶默认带着公共访问拦截配置,先删除桶级的拦截:
bash复制aws s3api delete-public-access-block \
--bucket myexample-public-images-2025
这里要注意,如果账号本身开启了账号级 Block Public Access,光删桶级可能不够。可以查询一下 S3 控制台的“Block Public Access”账号设置,确认账号级总开关没有强行打开。
接着写策略文件,存成 policy.json:
json复制{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadOnlyImages",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::myexample-public-images-2025/public/*"
}
]
}
把策略文件交给 S3:
bash复制aws s3api put-bucket-policy \
--bucket myexample-public-images-2025 \
--policy file://policy.json
看到不报错,基本就意味着公开展示策略已经生效。
4.3 上传图片并检查 Content-Type
上传本地图片到 public 前缀下:
bash复制aws s3 cp ./demo.jpg s3://myexample-public-images-2025/public/demo.jpg
上传完毕后,用 head-object 看看对象元数据:
bash复制aws s3api head-object \
--bucket myexample-public-images-2025 \
--key public/demo.jpg
在返回结果里找到 ContentType 字段。正常情况下它应该长这样:
json复制{
"ContentType": "image/jpeg"
}
如果没有 Content-Type,或者显示成了 binary/octet-stream,浏览器就不知道这是图片,从而直接下载而不是预览。这时候可以重新上传并显式指定:
bash复制aws s3 cp ./demo.jpg s3://myexample-public-images-2025/public/demo.jpg \
--content-type image/jpeg
这一步对“通过链接直接打开图片”非常关键,很多人在 CLI 环境里忽略了,结果上传的是二进制流,浏览器自然选择下载而不是渲染。
最终直链格式是:
text复制https://myexample-public-images-2025.s3.ap-northeast-1.amazonaws.com/public/demo.jpg
5. 对象直链和静态网站 URL,到底用哪个
5.1 对象直链:最稳妥的默认方案
我们上面复制出来的那个链接,是 S3 的对象 URL。它对应的是对象本身,响应的就是文件内容,没有任何“网页外壳”。这个直链的好处是简单、直接、稳定,也很适合放在 <img> 标签的 src 里。
浏览器能否预览,基本依赖响应头里的 Content-Type。如果只是放到 CDN 上游,或者给 IM 客户端发一个分享链接,我非常推荐用对象直链。
5.2 静态网站托管 URL:只在有页面需求时使用
有些人会想打开图片时看到的是一个网页,或者想实现根路径访问某个首页文件,这时才会考虑开启静态网站托管功能。S3 可以托管静态网站,需要开启属性里的“静态网站托管”,设置索引文档为 index.html,然后拿到一个类似这样的端点:
text复制http://你的桶名.s3-website-ap-northeast-1.amazonaws.com
但静态网站端点只支持 HTTP,不是 HTTPS,这一点对很多场景来说不太友好。你要做正式网站,最好通过 CDN 去终结 HTTPS。传图片然后直接打开的场景,不需要这套复杂配置,直接用对象直链就好。把对象 URL 放在浏览器地址栏能打开,其实就是一个天然可用的公网访问链接。
6. 常见问题排查,按优先级整理给你一份避坑清单
6.1 控制台点图片链接却提示 Access Denied
这种问题最高频,基本是权限链路上某个环节没打通。按照下面的顺序检查:
| 检查项 | 具体操作 |
|---|---|
| Block Public Access | 确认桶权限页面里的“阻止所有公共访问”是关闭状态 |
| 账号级拦截 | 检查 S3 控制台左边的 Block Public Access 账号设置 |
| 桶策略 Resource | 检查 ARN 中的桶名和通配符路径是否和对象 key 一致 |
| 前缀匹配 | 若对象实际在根目录,统计 policy 里写 /public/* 就不行 |
| 名称大小写 | S3 对象 key 区分大小写 |
这里最坑的是某些账号可能在 AWS 组织层面启用了公共访问拦截,这种情况下你在桶级别不管怎么关开关,只要账号级策略仍在阻止,就无法创建公开策略。需要由管理员在 S3 控制台或 AWS Organizations 中处理,不要自己盲目折腾很久。
6.2 链接没有 403,但浏览器直接下载文件
这是一类容易被误认为权限问题的“元数据问题”。单击对象详情,找到“元数据”区域,看 Content-Type 字段是否存在,是否与文件格式一致。
如果这个头没有,或者不匹配,就需要在上传时补上:
bash复制aws s3 cp ./demo.png s3://桶名/public/demo.png \
--content-type image/png
如果你曾经设置过 Content-Disposition: attachment,也会导致浏览器强制下载。这种一般是有人为下载需求主动设置的,如果希望图片直接预览,不应该加这个响应头。
6.3 明明设置了公有读,上传新图片后还是打不开
如果走的是对象 ACL 方案,没开启 ACL 的桶会直接不生效;如果走的是桶策略方案,按理说新上传到匹配前缀的对象应该自动可读,不存在“每张图要单独授权”的问题。遇到上传后打不开,我先检查对象 key 是否落在公共前缀下。比如我建了一个 public 文件夹,但上传时手滑点到了桶根路径,就会出现“规则写了图片”,但“图片根本没进入规则范围”的尴尬局面。
再说一个能应急的排查方法:控制台里可以选中对象,使用“使用 STS 临时会话打开”或“复制预签名 URL”,这类链接是临时授权的,几秒钟就能确认对象是否本身可读。如果预签名 URL 能打开,而普通直链打不开,那一定是权限配置问题;如果预签名 URL 也打不开,那就要往对象本身的状态查。
6.4 给别人发链接,第一分钟能开,过几分钟又不行
这种情况基本不是权限配置问题,更像是你在测试时用的其实是“预签名 URL”。预签名 URL 在有一定时效性,默认可能只有 3600 秒,超过时间就会过期,重新生成或修改有效期即可。这也是 S3 安全性的体现:在没有公共策略时,系统可以用临时授权让指定的人在指定时间内拿到访问权,但过期作废。如果你的目的就是长期公开直链,应该把重点放在配置桶策略上,而不是依赖控制台预览时生成的临时链接。
7. 别把公开桶变成裸奔桶:安全边界和账单经验
7.1 用前缀策略和“禁止 ListBucket”守住底裤
Public 并不等于整个桶可以随便被人扒目录。我们的桶策略里只写 s3:GetObject,没有写 s3:ListBucket,这意味着匿名用户只能尝试读取已知 key,不能通过 API 列出桶里所有文件。对方需要具体的图片 URL 才能访问,这非常适合“分享图册”“网页素材引用”这类场景。
再进一步,所有需要公开的文件统一放到 public/ 下,隐私文件放到另一个桶或另一个前缀,这样你只要保证权限策略没有被错误改大,日常使用时不容易发生数据泄露。我不建议把整桶 /* 公开,除非桶里所有内容本来就是不敏感素材。这个“最小权限 + 目录隔离”的习惯,能帮你挡掉很多次误操作。
7.2 流量费和请求费要心里有数
S3 存储费用本身不高,但公开链接一旦被放在高流量场景中,比如贴上热搜、被论坛转载,产生的请求数和出网流量费用可能让你月底看到账单后愣住。S3 对读请求是有计费项的,公网流出流量也有单价。
我遇到过几次测试桶被爬虫盯上的情况,下载量一夜之间飙升。S3 支持启用访问日志,也可以开启 CloudTrail 数据事件来记录读取请求,不过日志本身会增加成本,建议对最低风险桶开启成本预警就够。如果你的公开文件访问会越来越大,更合理的架构是在 S3 前再加一层 CDN,通过 CDN 缓存图片。这样 S3 本身的流出流量大幅降低,最终访问速度通常也更快。
7.3 权限改错了怎么快速回滚
万一发现某张图或某个桶被错误公开,最快的操作是重新打开 Block Public Access。在“权限”页面把“阻止所有公共访问”开启后,匿名访问立刻会被掐断。这个开关像一道保险丝,先把电断了,再慢慢去排查是哪条策略写得有问题。
不过要注意,Block Public Access 不等于删除策略。你之后重新关掉开关时,旧的公开策略仍然有可能让对象恢复可读,所以要检查并清理桶策略里多余的 Public 规则。
对我来说,S3 公开访问配置并不难,难的是养成一开始就把目录结构、权限策略、元数据一并考虑的习惯。多花两分钟隔离一个 public/ 前缀,把 Content-Type 确认清楚,比出了问题再去翻 XML 报错和 CloudTrail 日志轻松得多。以后谁再发来“图片传到 S3 了但链接打不开”这种问题,十有八九就是上面几个点中的一个,照着这条链路查一遍基本就能解决。
