跳到主要内容
孤云
语言 简中 繁中 EN

阿里云 OSS

大约 10 分钟、不需要花钱,就能在阿里云上给孤云准备好一个存储桶,以及一对只授权这个桶的密钥。本文是官方引导;控制台界面请以你打开时为准。

新手建议从海外地域(例如香港 cn-hongkong)开始:不需要域名、备案或证书。想要内地速度,先读「内地地域的两个硬约束」。

要准备什么

  • 一个阿里云账号;没有就先注册,并完成实名认证(个人可用身份证)。
  • 一个付款方式。开通 OSS 本身是 ¥0.00;费用只产生在实际存储量、请求次数与外网流量上。
  • 一个全局唯一的桶名,例如 your-bucket-name:3–63 位小写字母、数字和短横线,首尾必须是字母或数字。
  • 只有内地路线才需要:一个已完成 ICP 备案的域名、HTTPS 证书,以及一台符合条件的内地服务器(轻量应用服务器 / ECS,包年包月 ≥3 个月、带公网带宽)用来领取备案服务码。
  • 建议用电脑操作:RAM 与 AccessKey 的控制台在电脑上顺手得多。

只走海外地域的话,不需要域名、备案、证书,也不需要多买服务器。

分步操作

  1. 第 1 步

    注册阿里云账号并完成实名认证

    • 打开阿里云注册页,用手机号或邮箱注册。
    • 完成实名认证(个人可用身份证);认证通过后才能开通 OSS。
    • 内地路线建议把域名、备案与 OSS 放在同一个账号,后面少一步。
  2. 第 2 步

    开通对象存储 OSS

    • 在控制台搜「对象存储 OSS」,进入产品页点「立即开通」。
    • 开通页列出的订单金额是 ¥0.00;必须勾选「我已阅读并同意…专属条款」,再点「去支付」。
    • 支付页确认金额 ¥0.00,完成后点「管理控制台」进入 OSS 控制台。开通本身不收费。
  3. 第 3 步

    创建存储桶(Bucket)

    • 在「Bucket 列表」点「创建 Bucket」。
    • 桶名全局唯一,例如 your-bucket-name:3–63 位小写字母、数字和短横线,首尾必须是字母或数字。
    • 地域选离你和主要使用者最近的。海外建议香港 cn-hongkong;内地请先读下面的硬约束。
    • 读写权限选「私有」,「阻止公共访问」保持开启。
    • 存储类型用「标准」,版本控制保持关闭(孤云不使用多版本)。
  4. 第 4 步

    创建 RAM 子用户并取密钥

    • 进入「访问控制 RAM」→「身份管理 → 用户」→「创建用户」。
    • 勾选「OpenAPI 调用访问」(较新的界面写作「使用永久 AccessKey 访问」)—— 都是指用 AccessKey 调用;不要勾选控制台登录。
    • 创建完成后,AccessKey ID 与 Secret 只显示一次,立刻复制保存;关掉页面就再也看不到 Secret。
    • 不要使用主账号(根账号)密钥,也不要挂 AliyunOSSFullAccess。
  5. 第 5 步

    挂最小权限策略

    • 在「权限管理 → 权限策略」里新建一条自定义策略,切到脚本(JSON)编辑方式。
    • 粘贴下面的最小权限策略,把 your-bucket-name 换成你的真实桶名。
    • 回到用户页面,把这条自定义策略授权给刚才建的子用户。
    • 不要把资源范围收窄到某个目录:孤云还要在 .guyun/ 前缀下写回收站清单、设备连接记录,以及跨设备交接用的 .guyun/handoff/;收窄会让这些写入失败。
    • 不要授予 oss:*、删桶、改 Bucket ACL 或管理历史版本;缩略图属于 oss:GetObject,不需要额外权限。
  6. 第 6 步

    把四个值填进孤云

    • 打开孤云的「添加存储」,服务商选「阿里云 OSS」。
    • 填:Bucket 名称 = 你的桶名;地域 = 桶所在地域(例如 cn-hongkong);Endpoint = https://oss-{地域}.aliyuncs.com;AccessKey ID 与 Secret = 刚才那对密钥。
    • 海外地域的地址方式用「Bucket 子域名」;绑了自定义域名就把 Endpoint 改成你的域名,地址方式选「自定义域名 CNAME」。
    • 密钥只保存在本机 Keychain,不上传、不同步;不要贴到聊天、文档或代码里。
  7. 第 7 步

    测试连接

    • 回到孤云点「测试连接」,它只调用 ListObjectsV2 列出一次根目录。
    • 读得通不代表写得进:建议在桶里建一个临时目录,做一次上传 / 下载 / 删除,再把临时目录删掉。
    • 连不上时对照下面的「常见错误」,按错误码排查。

四条路径

先挑一条符合你现有条件的路线 —— 各条的接入工作量差别很大。

路径需要准备代价适合谁
海外地域(如香港 cn-hongkong)只要 OSS 与一对密钥内地访问跨境,速度略慢推荐新手,没有备案问题
内地地域 + 自定义域名已备案的域名 + HTTPS 证书 + 一台符合条件的内地服务器(换备案服务码)备案要 1–20 个工作日,还要额外买服务器有域名、要内地速度的人
内地地域 + 默认域名仅限 2025-03-20 之前开通 OSS 的账号新账号不可用,会报 PublicEndpointForbidden老账号
不想看这些,走 ROS只要 OSS多两次点击(复制并粘贴模板)想省事的人

最小权限 RAM 策略

把这段 JSON 粘进你正在新建的自定义策略(脚本编辑)。它只授权这一个桶的读写,不含删桶与 Bucket 配置。

json
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["oss:ListObjects", "oss:GetBucketLocation", "oss:ListMultipartUploads"],
      "Resource": ["acs:oss:*:*:your-bucket-name"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:GetObject", "oss:PutObject", "oss:DeleteObject", "oss:AbortMultipartUpload", "oss:ListParts"],
      "Resource": ["acs:oss:*:*:your-bucket-name/*"]
    }
  ]
}

把 your-bucket-name 换成真实桶名 —— 这一处没替换是新手最常见的 403 原因。

挂策略时的三条红线

不要把资源范围收窄到某个目录。 除了你自己的文件,孤云还要在 .guyun/ 前缀下写回收站清单、设备连接记录,以及跨设备交接用的短命记录 .guyun/handoff/;收窄会让这些写入失败。

不要授予 oss:* 或 AliyunOSSFullAccess,也不要允许删桶、改 Bucket ACL 或管理历史版本。 缩略图属于 oss:GetObject,上面的策略已经够用。

不要使用主账号密钥。 给孤云单独建一个 RAM 子用户,只授权这一个桶。

内地地域的两个硬约束

① 备案服务码:OSS 不提供。 给 OSS 的域名办备案时要选一个备案服务码,而服务码只由符合条件的内地服务器产品赠送(轻量应用服务器 / ECS,内地节点、包年包月且时长 ≥3 个月、已购公网带宽)。OSS 不在名单里,免费试用与按量付费的 ECS 也不符合条件。域名若在境外注册商(GoDaddy、Namecheap、Cloudflare 等),还要先转入境内注册商才能备案。

② PublicEndpointForbidden:默认外网域名被拒。 2025-03-20 起新开通 OSS 的账号,用默认外网域名(例如 oss-cn-hangzhou.aliyuncs.com)访问内地 Bucket 的数据类 API(上传、下载都算)一律被拒。它限制的是域名,不是权限:桶设为私有、阻止公共访问、不对外提供任何链接,一样受限;判断依据是 OSS 的开通时间,不是建桶时间,与桶权限无关;S3 兼容域名 s3.oss-{地域}.aliyuncs.com 同样受限。

要留在内地地域,就得绑定一个已备案的自定义域名(并配置 HTTPS 证书);否则请换香港或其他海外地域 —— 那里的默认域名不受这条限制。

ROS 一键路线:一次建好桶、RAM 用户和密钥

不想一步步点,就让阿里云的资源编排(ROS)按下面这份模板一次建好:私有且阻止公共访问的桶、只授权该桶的 RAM 用户,以及这个用户的 AccessKey。完成后从资源栈的「输出」里复制 AccessKey ID 与 Secret。

  • 打开 ROS 控制台:https://ros.console.aliyun.com/。国际站账号用 ros.console.alibabacloud.com。
  • 复制下面的模板全文,在创建页选「指定模板 → 输入模板」,模板内容格式选 ROS + JSON,粘贴进去再点下一步。
  • 坑 1:依赖要显式写。 模板里给策略与 AccessKey 指定 RAM 用户时,用 Ref 取参数里的用户名不会让 ROS 自动建立依赖,必须显式写 DependsOn: AppUser(这份模板里已经写好,并用 Fn::GetAtt 引用资源属性);改模板时把这两处丢掉,会报 The user … is not exists 一类的校验错误。
  • 坑 2:RAM 的 DisplayName 上限是 12 个字符(官方文档写 1–128,实际 API 只允许 12,中文按 1 个算)。超过会在校验阶段被拦下;这份模板用的是固定值「孤云存储」,桶名放在备注里。
  • 坑 3:创建页下方有一个「安全确认」勾选框(「我确认,阿里云 ROS 可能创建 RAM 资源」),不勾选无法创建。资源栈名保持默认即可(形如 guyun_2026-09-11_xxxx)。
  • 失败时点资源栈的「事件」看原因:桶名被占用最常见;报 The Parameter (BucketName) was not defined in template 说明模板没粘进去(回第 1 步重来);漏勾「安全确认」也会被拦下。
  • 同一账号里再建一个桶时,把创建页里的 RAM 用户名 从默认的 guyun-app 改成别的(例如 guyun-{bucket})—— 同名用户已存在时创建会失败。
  • 删资源栈会连同它建的桶与 RAM 用户一起删掉,桶里有数据时不要随手删。

ROS 模板(原文)

复制全文,粘贴到 ROS 创建页的「输入模板」里。

json
{
  "ROSTemplateFormatVersion": "2015-09-01",
  "Description": {
    "zh-cn": "为孤云创建:私有且阻止公共访问的 OSS Bucket、只授权该桶读写的 RAM 用户、以及该用户的 AccessKey。AccessKey ID 与 Secret 会出现在资源栈的「输出」里,复制回应用即可。",
    "en": "Creates, for Guyun: a private OSS bucket with public access blocked, a RAM user limited to that bucket, and an AccessKey for it. The AccessKey ID and secret appear in the stack outputs; copy them back into the app."
  },
  "Parameters": {
    "BucketName": {
      "Type": "String",
      "Label": {
        "zh-cn": "Bucket 名称",
        "en": "Bucket name"
      },
      "Description": {
        "zh-cn": "全局唯一。3–63 位,只能用小写字母、数字和短横线,首尾必须是字母或数字。例如 my-photos-2026。",
        "en": "Globally unique. 3–63 characters: lowercase letters, digits and hyphens, starting and ending with a letter or digit. For example my-photos-2026."
      },
      "AllowedPattern": "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$",
      "MinLength": 3,
      "MaxLength": 63
    },
    "RamUserName": {
      "Type": "String",
      "Label": {
        "zh-cn": "RAM 用户名",
        "en": "RAM user name"
      },
      "Description": {
        "zh-cn": "给应用专用的子账号。如果账号下已经有同名用户,请改成别的名字再创建。",
        "en": "A dedicated sub-account for the app. If a user with this name already exists, pick another one."
      },
      "Default": "guyun-app",
      "AllowedPattern": "^[a-zA-Z0-9._-]{1,64}$",
      "MinLength": 1,
      "MaxLength": 64
    }
  },
  "Resources": {
    "Bucket": {
      "Type": "ALIYUN::OSS::Bucket",
      "Properties": {
        "BucketName": {
          "Ref": "BucketName"
        },
        "AccessControl": "private",
        "StorageClass": "Standard",
        "BlockPublicAccess": true
      }
    },
    "AppUser": {
      "Type": "ALIYUN::RAM::User",
      "Properties": {
        "UserName": {
          "Ref": "RamUserName"
        },
        "DisplayName": {
          "Fn::Sub": "孤云存储"
        },
        "Comments": {
          "Fn::Sub": "孤云专用存储用户;授权桶 ${BucketName} 的读写;随资源栈删除。"
        }
      }
    },
    "BucketPolicy": {
      "Type": "ALIYUN::RAM::ManagedPolicy",
      "DependsOn": "AppUser",
      "Properties": {
        "PolicyName": {
          "Fn::Sub": "guyun-${BucketName}-rw"
        },
        "Description": {
          "Fn::Sub": "Guyun: read and write ${BucketName} only. No bucket-level administration."
        },
        "PolicyDocument": {
          "Version": "1",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "oss:ListObjects",
                "oss:GetBucketLocation",
                "oss:ListMultipartUploads"
              ],
              "Resource": [
                {
                  "Fn::Sub": "acs:oss:*:*:${BucketName}"
                }
              ]
            },
            {
              "Effect": "Allow",
              "Action": [
                "oss:GetObject",
                "oss:PutObject",
                "oss:DeleteObject",
                "oss:AbortMultipartUpload",
                "oss:ListParts"
              ],
              "Resource": [
                {
                  "Fn::Sub": "acs:oss:*:*:${BucketName}/*"
                }
              ]
            }
          ]
        },
        "Users": [
          {
            "Fn::GetAtt": [
              "AppUser",
              "UserName"
            ]
          }
        ]
      }
    },
    "AccessKey": {
      "Type": "ALIYUN::RAM::AccessKey",
      "DependsOn": "AppUser",
      "Properties": {
        "UserName": {
          "Fn::GetAtt": [
            "AppUser",
            "UserName"
          ]
        }
      }
    }
  },
  "Outputs": {
    "BucketName": {
      "Label": {
        "zh-cn": "Bucket 名称",
        "en": "Bucket name"
      },
      "Description": {
        "zh-cn": "填到应用的「Bucket 名称」里。",
        "en": "Paste into the app's bucket name field."
      },
      "Value": {
        "Ref": "BucketName"
      }
    },
    "RegionId": {
      "Label": {
        "zh-cn": "地域",
        "en": "Region"
      },
      "Description": {
        "zh-cn": "资源栈所在地域,也就是 Bucket 的地域。",
        "en": "The stack's region, which is the bucket's region."
      },
      "Value": {
        "Ref": "ALIYUN::Region"
      }
    },
    "Endpoint": {
      "Label": {
        "zh-cn": "HTTPS Endpoint",
        "en": "HTTPS endpoint"
      },
      "Description": {
        "zh-cn": "走默认域名时填到应用的「HTTPS Endpoint」里(地址方式选「Bucket 子域名」);如果之后绑了自定义域名,Endpoint 要改成那个域名。",
        "en": "With the default endpoint, paste this into the app's HTTPS endpoint field (addressing: virtual host). If you bind a custom domain later, the endpoint becomes that domain instead."
      },
      "Value": {
        "Fn::Sub": "https://oss-${ALIYUN::Region}.aliyuncs.com"
      }
    },
    "AccessKeyId": {
      "Label": {
        "zh-cn": "AccessKey ID",
        "en": "AccessKey ID"
      },
      "Description": {
        "zh-cn": "复制后填到应用的「AccessKey ID」里。",
        "en": "Copy into the app's AccessKey ID field."
      },
      "Value": {
        "Fn::GetAtt": [
          "AccessKey",
          "AccessKeyId"
        ]
      }
    },
    "AccessKeySecret": {
      "Label": {
        "zh-cn": "AccessKey Secret",
        "en": "AccessKey secret"
      },
      "Description": {
        "zh-cn": "只在这里显示,复制后填进应用的 Keychain;不要粘贴到聊天、文档或代码里。",
        "en": "Shown here only. Copy it into the app's Keychain; never paste it into a chat, document or repository."
      },
      "Value": {
        "Fn::GetAtt": [
          "AccessKey",
          "AccessKeySecret"
        ]
      }
    }
  },
  "Metadata": {
    "ALIYUN::ROS::Interface": {
      "ParameterGroups": [
        {
          "Parameters": [
            "BucketName"
          ],
          "Label": {
            "default": "OSS"
          }
        },
        {
          "Parameters": [
            "RamUserName"
          ],
          "Label": {
            "default": "RAM"
          }
        }
      ],
      "Outputs": [
        "BucketName",
        "RegionId",
        "Endpoint",
        "AccessKeyId",
        "AccessKeySecret"
      ]
    }
  }
}

这份模板与上面的手工路线做出完全一样的资源和权限:私有且阻止公共访问的桶、只授权该桶的策略、为该用户创建的 AccessKey。模板内容就在本页,不需要信任任何第三方托管。

抄下这四个值

这四个值要填进孤云的「添加存储」页:

抄下这个值填进孤云的哪一格
桶名
your-bucket-name(换成你的真实桶名)
桶名称
地域
cn-hongkong
地域 / Region
端点
https://oss-cn-hongkong.aliyuncs.com
Endpoint
密钥
RAM 子用户的 AccessKey ID 与 Secret
AccessKey ID 与 Secret

怎么知道成了

  • 孤云的「测试连接」返回成功,能列出桶的根目录(空桶返回空列表也算成功)。
  • 想完整验证:在桶里建一个临时目录,上传一个文件、下载一次、再删掉 —— 三步都通过,读写权限就齐了。
  • 常见错误都不出现:没有 403,没有 PublicEndpointForbidden,没有 SignatureDoesNotMatch。

回到孤云,点「测试连接」。

常见错误

在「测试连接」里看到这些错误码时,按下面的顺序排查。

现象 / 错误码含义怎么办
403 AccessDenied密钥没权限,或自定义策略里的桶名没替换成真实桶名;也可能策略没授权给这个子用户回第 5 步核对:your-bucket-name 是否已替换、策略是否挂在这个用户上、密钥是否属于该用户
PublicEndpointForbidden内地域 + 默认外网域名 + OSS 在 2025-03-20 之后开通绑定已备案的自定义域名(Endpoint 换成该域名、地址方式选 CNAME),或把桶换到香港 / 其他海外地域
SignatureDoesNotMatch / InvalidAccessKeyId密钥复制错了(Secret 只显示一次,没存下就再也拿不到),或 ID 与 Secret 不是同一对在 RAM 用户下重新创建一对 AccessKey,把两个值一起复制回孤云
连接测试通过,但上传失败测试只调用了 ListObjectsV2 —— 读得通不代表写得进确认策略里有 oss:PutObject / oss:DeleteObject,再在临时目录里做一次上传 / 下载 / 删除

错误码不在表里时,把完整报错和 Request ID 一起记下来 —— 提工单时要用。