目录
一、OSS 是什么
二、三个核心词
三、企业级铁律
四、内容了解
五、最小权限策略思路(示例)
六、常见坑
七、OSS STS 直传上传完整示例
官方参考:权限与访问控制 · 降低恶意流量费用风险
一、OSS 是什么
OSS(Object Storage Service) = 阿里云的海量网盘/对象仓库。
和 ECS 云盘不同:
| ECS 云盘 | OSS | |
|---|---|---|
像什么 | 插在服务器上的硬盘 | 云上的大仓库 |
访问方式 | 挂载成盘符/路径 | 按“对象名”上传下载(HTTP/API) |
适合 | 系统、数据库本地文件 | 图片、视频、备份、日志包、静态网站 |
扩展 | 有容量上限、跟机器绑定感强 | 近乎无限扩展 |
一句话:
ECS 负责算,OSS 负责存(尤其是海量非结构化文件)。
二、三个核心词
| 概念 | 描述 | 例子 |
|---|---|---|
Bucket(存储空间) | 一个仓库/桶 |
|
Object(对象) | 仓库里的一个文件 |
|
Endpoint(访问域名) | 仓库地址入口 | 外网/内网/加速域名 |
对象名看起来像路径(a/b/c.jpg),本质是对象的 Key(名字),不是真正的文件夹层级。
三、企业级铁律
- 默认私有 Bucket,非必要不开公共读/公共读写
- 开启阻止公共访问(防误配泄露)
- ECS 访问同地域 OSS 走内网 Endpoint(更快、通常更省流量费)
- 程序用 STS / 实例角色,少把长期 AK 塞进客户端
- 按前缀分目录 + 生命周期,冷数据自动降成本
- 重要数据开版本控制 / 跨区域复制(防误删、做容灾)
- 对外资源配防盗链,防被盗刷流量
- 打标签 + 分 Bucket:生产/测试、公开静态/私密附件分开
四、内容了解
1、创建企业规范的 Bucket
示例:
| 项 | 建议 |
|---|---|
名称 | 全局唯一,如 |
地域 | 与 ECS/ACK 同地域(内网访问) |
ACL | 私有 |
阻止公共访问 | 开启 |
版本控制 | 重要业务开启 |
服务端加密 | 按合规选择 OSS 托管密钥或 KMS |
标签 |
|
常见拆法:
xxx-static:可经 CDN 分发的静态资源xxx-private:合同、证件、用户私密附件xxx-backup:备份归档xxx-logs:日志归档
2、权限怎么控(和 RAM 串起来)
OSS 权限常见三层:
| 方式 | 管什么 | 企业用法 |
|---|---|---|
Bucket ACL | 桶级粗权限(私有/公共读等) | 默认私有 |
RAM Policy | 某个用户/角色能访问哪些桶/操作 | 管“这个人能干什么” |
Bucket Policy | 挂在桶上的策略(可含匿名、IP、VPC 条件) | 管“这个桶允许谁来” |
还有:
- 阻止公共访问:安全兜底
- 防盗链 Referer:防外站盗图盗流量
- CORS:浏览器前端直传时需要
原则:
人 / 程序授权看 RAM;桶的特殊规则看 Bucket Policy;能私有就私有。
3、谁怎么访问 OSS(企业最常见 4 种)
方式 A:ECS/应用服务端访问(推荐)
ECS 绑定 RAM 角色 → 用 STS 临时凭证 → 内网 Endpoint 访问 OSS
优点:无长期 AK 写代码,更安全。
方式 B:App/浏览器直传(推荐 STS)
客户端 → 业务服务器申请临时凭证
业务服务器 → AssumeRole 拿 STS
客户端 → 用 STS 直传 OSS(不经业务服务器扛文件流)
可在 STS Policy 里限制只能传user001/*这类前缀,做多租户隔离。
方式 C:签名 URL(限期下载/上传)
适合:私有文件临时给用户下载,URL 过期失效。
方式 D:公共读 + CDN(谨慎)
仅适合真正公开的静态资源,并务必:
- 防盗链
- 最好前面挂 CDN
- 评估是否必须公开
官方:STS 临时授权访问 OSS · 客户端直传
4、内外网 Endpoint(省钱关键点)
| 访问来源 | 应用 Endpoint |
|---|---|
同地域 ECS/ACK | 内网 Endpoint |
用户浏览器、外网系统 | 外网 Endpoint 或 CDN/加速域名 |
全球远距离上传下载 | 传输加速域名(按需,有成本) |
同地域 ECS 误走外网,可能又慢又多花钱。
5、存储类型与生命周期(降本)
常见类型(从热到冷):
| 类型 | 适合 |
|---|---|
标准 | 经常访问的图片、文件 |
低频 | 访问少,但要较快取回 |
归档 / 冷归档 / 深度冷归档 | 很少访问的备份、合规存档(取回可能要时间) |
生命周期规则示例:
- 日志:
logs/30 天后转低频,180 天后删除 - 备份:
backup/7 天后转归档 - 临时上传:
tmp/3 天后删除
企业要有人负责:否则桶会越存越贵。
6、防误删与容灾
| 能力 | 作用 |
|---|---|
版本控制 | 覆盖/删除还可找回历史版本 |
跨区域复制 | 异步复制到另一地域,异地容灾/合规 |
WORM(合规保留) | 规定期内不能删改(强合规场景) |
快照式思维 | 重要前缀定期检查复制状态与权限 |
注意:跨区域复制通常要求源/目标版本控制状态等条件匹配(按控制台与文档配置)。
7、和企业架构怎么拼
用户上传图片
→ App/Web
→(STS 直传)OSS 私有桶
→ 需要展示时:签名 URL 或经业务鉴权后回源
→ 热点静态:CDN → OSS
ECS 应用读写文件
→ 同地域内网 Endpoint + 实例角色
ECS 系统备份/日志包
→ 定期上传 OSS,生命周期转归档
和前面内容的关系:
- RAM/STS:谁能访问 OSS
- VPC:可用 Bucket Policy 限制仅某 VPC 访问(更严场景)
- ECS:算力侧;大文件别长期堆系统盘,放 OSS
- ALB/ESS:无状态应用,用户文件进 OSS,机器才能随便扩缩
五、最小权限策略思路(示例)
应用只读某个桶:
- 允许:
oss:GetObject、oss:ListObjects(如需列表)- 资源:
acs:oss:*:*:bucket-name、acs:oss:*:*:bucket-name/*上传服务只写某前缀:
- 允许:
oss:PutObject- 资源:
acs:oss:*:*:bucket-name/uploads/*生产避免:
oss:*+Resource: *。
六、常见坑
| 坑 | 结果 |
|---|---|
Bucket 公共读忘关 | 数据泄露或被盗刷流量,费用暴涨 |
ECS 走外网 Endpoint | 慢 + 可能产生不必要流量费 |
AK 写进 App | 泄露后可被拖桶 |
无生命周期 | 成本 silently 上涨 |
当“网盘文件夹”强依赖本地路径语义 | 对象存储不是 POSIX 文件系统 |
前端直传未配 CORS | 浏览器被拦 |
小结
OSS 是云上对象仓库:私有为先,RAM/STS 控权,同地域走内网,生命周期降本,版本与跨区域做保险,公开资源才谨慎配合 CDN/防盗链。
七、OSS STS 直传上传完整示例
流程与代码结构参考官方文档:客户端直传 · STS 临时授权
1、整体流程
浏览器/App
│ ① 请求临时凭证(可带 userId)
▼
业务服务器(持有 RAM 用户 AK,或 ECS 角色)
│ ② AssumeRole(可附加更严 Policy)
▼
STS 返回:AccessKeyId + AccessKeySecret + SecurityToken + Expiration
│ ③ 把临时凭证返回客户端(不要返回长期 AK)
▼
客户端用 OSS SDK 直传文件到 OSS
│ ④ PutObject / 分片上传
▼
OSS 存好,返回成功(文件不经过业务服务器)
2、前置准备(必须先做)
1)Bucket
- 地域:如
cn-hangzhou - ACL:私有
- 建议开启:阻止公共访问
2)配置 CORS(Web 直传必做)
Bucket → 数据安全 → 跨域设置,示例:
| 项 | 值 |
|---|---|
来源 |
|
Methods |
|
Allow Headers |
|
Expose Headers |
|
缓存时间 |
|
3)创建“上传角色” RAM Role
- RAM → 角色 → 创建角色
- 信任主体:选择 阿里云账号(或你的业务账号)
- 角色名:如
oss-web-upload - 记下 RoleArn:
acs:ram::<账号ID>:role/oss-web-upload
给该角色挂权限策略(示例:只能写指定桶的uploads/):
{ "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "oss:PutObject", "oss:InitiateMultipartUpload", "oss:UploadPart", "oss:CompleteMultipartUpload", "oss:AbortMultipartUpload", "oss:ListParts" ], "Resource": [ "acs:oss:*:*:your-bucket-name/uploads/*" ] } ] }把your-bucket-name换成真实桶名。
4)创建“发证账号” RAM 用户(给业务服务器用)
- 创建 RAM 用户,只开 OpenAPI 访问,拿到 AK/SK
- 授权:
AliyunSTSAssumeRoleAccess(允许调用 AssumeRole) - 不要把这套 AK 给前端
生产更推荐:业务跑在 ECS 上时,用 ECS 实例角色 调 STS,服务器上也尽量无长期 AK。
3、服务端完整示例(Node.js)
安装
npm i express ali-oss dotenv cors.env
ALIBABA_CLOUD_ACCESS_KEY_ID=你的RAM用户AK ALIBABA_CLOUD_ACCESS_KEY_SECRET=你的RAM用户SK OSS_ROLE_ARN=acs:ram::123456789012****:role/oss-web-upload OSS_BUCKET=your-bucket-name OSS_REGION=oss-cn-hangzhou STS_DURATION_SECONDS=3600 PORT=3000server.js
require('dotenv').config(); const express = require('express'); const cors = require('cors'); const { STS } = require('ali-oss'); const app = express(); app.use(cors({ origin: true })); // 生产请改成明确前端域名 app.use(express.json()); const { ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET, OSS_ROLE_ARN, OSS_BUCKET, OSS_REGION, STS_DURATION_SECONDS = '3600', } = process.env; /** * 按用户收紧权限:只能上传到 uploads/{userId}/* * 这是企业直传的关键:防止拿到 token 后乱传整个桶 */ function buildSessionPolicy(userId) { const prefix = `uploads/${userId}/`; return { Version: '1', Statement: [ { Effect: 'Allow', Action: [ 'oss:PutObject', 'oss:InitiateMultipartUpload', 'oss:UploadPart', 'oss:CompleteMultipartUpload', 'oss:AbortMultipartUpload', 'oss:ListParts', ], Resource: [`acs:oss:*:*:${OSS_BUCKET}/${prefix}*`], }, ], }; } app.get('/api/oss/sts', async (req, res) => { try { // 实际项目里应从登录态取 userId,这里演示用 query const userId = String(req.query.userId || 'anonymous').replace(/[^\w-]/g, ''); if (!userId) { return res.status(400).json({ message: 'userId required' }); } const sts = new STS({ accessKeyId: ALIBABA_CLOUD_ACCESS_KEY_ID, accessKeySecret: ALIBABA_CLOUD_ACCESS_KEY_SECRET, }); const policy = buildSessionPolicy(userId); const result = await sts.assumeRole( OSS_ROLE_ARN, policy, // 会话策略:比角色权限更严 Number(STS_DURATION_SECONDS), // 900~3600 常见 `web-upload-${userId}` // RoleSessionName ); const c = result.credentials; res.json({ region: OSS_REGION, bucket: OSS_BUCKET, dir: `uploads/${userId}/`, // 前端拼 objectKey 用 accessKeyId: c.AccessKeyId, accessKeySecret: c.AccessKeySecret, stsToken: c.SecurityToken, expiration: c.Expiration, }); } catch (err) { console.error(err); res.status(500).json({ message: err.message || 'assumeRole failed' }); } }); app.listen(process.env.PORT || 3000, () => { console.log(`STS server on http://localhost:${process.env.PORT || 3000}`); });启动:
node server.js自测:
curl "http://localhost:3000/api/oss/sts?userId=u1001"4、浏览器端完整示例
安装(前端项目)
npm i ali-ossupload.js(可直接在页面里用)
import OSS from 'ali-oss'; async function fetchSts(userId) { const resp = await fetch( `http://localhost:3000/api/oss/sts?userId=${encodeURIComponent(userId)}`, { credentials: 'include' } // 若你后面加了登录 cookie ); if (!resp.ok) throw new Error('get sts failed'); return resp.json(); } async function createOssClient(userId) { const sts = await fetchSts(userId); return { dir: sts.dir, client: new OSS({ region: sts.region, // 如 oss-cn-hangzhou bucket: sts.bucket, accessKeyId: sts.accessKeyId, accessKeySecret: sts.accessKeySecret, stsToken: sts.stsToken, secure: true, // 凭证临近过期自动刷新(SDK 能力,建议保留) refreshSTSTokenInterval: 300000, refreshSTSToken: async () => { const next = await fetchSts(userId); return { accessKeyId: next.accessKeyId, accessKeySecret: next.accessKeySecret, stsToken: next.stsToken, }; }, }), }; } /** 小文件:简单上传 */ export async function uploadSimple(file, userId) { const { client, dir } = await createOssClient(userId); const objectKey = `${dir}${Date.now()}_${file.name}`; const result = await client.put(objectKey, file); return { name: result.name, url: result.url, // 私有桶此 URL 不能直接匿名打开,需签名 URL 访问 }; } /** 大文件:分片上传(STS 直传的优势场景) */ export async function uploadMultipart(file, userId, onProgress) { const { client, dir } = await createOssClient(userId); const objectKey = `${dir}${Date.now()}_${file.name}`; const result = await client.multipartUpload(objectKey, file, { progress: (p) => onProgress?.(Math.floor(p * 100)), partSize: 1024 * 1024, // 1MB,可按网络调整 }); return { name: result.name, result }; } /** 私有文件:需要展示时,让服务端另发签名 URL,或临时 GetObject 权限 */最简 HTML 用法思路
<input type="file" id="f" /> <button id="btn">上传</button> <script type="module"> import { uploadSimple, uploadMultipart } from './upload.js'; document.getElementById('btn').onclick = async () => { const file = document.getElementById('f').files[0]; if (!file) return alert('请选择文件'); const userId = 'u1001'; // 实际从登录态取 const isLarge = file.size > 5 * 1024 * 1024; const ret = isLarge ? await uploadMultipart(file, userId, (p) => console.log(p + '%')) : await uploadSimple(file, userId); console.log('上传成功', ret); alert('上传成功: ' + ret.name); }; </script>5、Python 服务端版(可选)
# pip install flask alibabacloud_sts20150401 alibabacloud_tea_openapi alibabacloud_credentials import json import os from flask import Flask, request, jsonify from alibabacloud_tea_openapi.models import Config from alibabacloud_sts20150401.client import Client as StsClient from alibabacloud_sts20150401 import models as sts_models from alibabacloud_credentials.client import Client as CredentialClient app = Flask(__name__) ROLE_ARN = os.environ["OSS_ROLE_ARN"] REGION = os.environ.get("OSS_REGION_ID", "cn-hangzhou") BUCKET = os.environ["OSS_BUCKET"] def session_policy(user_id: str): prefix = f"uploads/{user_id}/" return json.dumps({ "Version": "1", "Statement": [{ "Effect": "Allow", "Action": [ "oss:PutObject", "oss:InitiateMultipartUpload", "oss:UploadPart", "oss:CompleteMultipartUpload", "oss:AbortMultipartUpload", "oss:ListParts" ], "Resource": [f"acs:oss:*:*:{BUCKET}/{prefix}*"] }] }) @app.get("/api/oss/sts") def get_sts(): user_id = request.args.get("userId", "anonymous") config = Config(region_id=REGION, credential=CredentialClient()) client = StsClient(config) req = sts_models.AssumeRoleRequest( role_arn=ROLE_ARN, role_session_name=f"web-upload-{user_id}", duration_seconds=3600, policy=session_policy(user_id), ) resp = client.assume_role(req) cred = resp.body.credentials return jsonify({ "region": f"oss-{REGION}", "bucket": BUCKET, "dir": f"uploads/{user_id}/", "accessKeyId": cred.access_key_id, "accessKeySecret": cred.access_key_secret, "stsToken": cred.security_token, "expiration": cred.expiration, }) if __name__ == "__main__": app.run(port=3000)环境变量需提供:ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET、OSS_ROLE_ARN、OSS_BUCKET。
6、上传后如何给用户看私有文件
私有桶直传成功后,对象不能公开访问。常见两种:
- 服务端生成签名 URL(限时下载/预览)
- STS 另发只读临时凭证(少见,权限要更小心)
签名 URL 思路(服务端,有 OSS 读权限的角色/用户):
// 伪代码:服务端用有 GetObject 权限的客户端 const url = client.signatureUrl(objectKey, { expires: 600 }); // 10 分钟7、企业落地检查清单
| 项 | 是否完成 |
|---|---|
Bucket 私有 + 阻止公共访问 | ☐ |
CORS 只放行业务前端域名 | ☐ |
上传角色权限最小化(仅 | ☐ |
AssumeRole 再加会话 Policy(按 userId 前缀) | ☐ |
长期 AK 只在服务端/实例角色,不下发前端 | ☐ |
接口需登录态,userId 不可被随便伪造 | ☐ |
限制文件类型/大小(前端+服务端二次校验) | ☐ |
STS 结果可短时缓存,避免疯狂 AssumeRole | ☐ |
同地域服务端访问 OSS 用内网 Endpoint | ☐ |
8、常见报错
| 现象 | 原因 |
|---|---|
浏览器 CORS 错误 | Bucket 未配跨域或来源域名不对 |
| 角色/会话 Policy 不含该前缀或 Action |
| 凭证过期,需刷新 STS |
能上传到别人前缀 | 会话 Policy 没按 userId 收紧,或 userId 未鉴权 |
上传成功但打不开 | 私有桶正常,需签名 URL |
小结
业务服务器只负责“发临时工牌(STS)”;文件由客户端直送 OSS。
企业关键是:角色最小权限 + 按用户前缀的会话策略 + CORS + 私有桶 + 前端永不持有长期 AK。
官方完整工程还可参考文档中的sts.zip/ 函数计算一键部署示例:
在客户端直接上传文件到 OSS