最近我在帮几个做后端的朋友排查一个问题,挺头疼的。大家上传文件到青云对象存储(QingCloud Object Storage,简称 QOS)或者底层兼容 S3 协议的服务时,前端一传文件就报 403 Forbidden,错误信息里还带着 SignatureDoesNotMatch 或者 ExpiredToken。一开始大家都以为是代码写错了,改来改去,最后发现是 STS 临时凭证的签名过期 或者 权限配置 没搞对。
今天我们就把这个事儿掰开揉碎了讲清楚,特别是 STS 临时凭证(Security Token Service) 这一块,因为它是解决前端直传、避免密钥泄露的关键,但也是最容易踩坑的地方。我会把原理、配置、代码示例和常见坑都列出来,保证你看完就能上手。
一、 为什么会出现 403 签名失效?
在深入 STS 之前,你得先明白 QOS 是怎么验证你的请求合法性的。QOS 底层兼容 Amazon S3 协议,所以它的签名机制也是 S3 风格的 HMAC-SHA256。
当你发起一个请求(比如上传文件、列出 Bucket 里的对象)时,请求头里会包含一个 Authorization 字段,长这样:
Authorization: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20231027/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=1234567890abcdef...
这个签名的生成过程大概是:
- 拼接待签名字符串(StringToSign):把 HTTP 方法、URI、Headers、查询参数等按特定格式组合。
- 用 Secret Access Key(SK)对字符串进行 HMAC-SHA256 哈希。
- 把结果编码成 Base64,拼上 Access Key(AK)和时间戳。
403 签名失效 通常意味着:服务器算出来的签名,和你请求里带的签名对不上。
主要原因有三类:
- 密钥错误:AK/SK 填错了,或者用的不是同一个账户的密钥对。
- 时间不同步:请求时间戳和服务器时间偏差太大(一般超过 15 分钟就会拒)。
- 临时凭证过期:这是 STS 场景最常见的,Token 用久了,或者生成后就没更新。
二、 STS 临时凭证:为什么用它?
以前,前端直接传文件,我们会把云账号的 永久 AK/SK 交给前端。这太危险了!别人抓个包,把你的 Key 拷走,就能随便删你东西。
STS 临时凭证 就是为了解决这个问题。你可以临时“借”给前端一个 AK + SK + Security Token,有效期只有 15 分钟到几小时,过期就废。就算 Key 泄露了,危害也是可控的。
STS 凭证包含什么?
| 字段 | 说明 |
|---|---|
AccessKeyId |
临时 AK,通常以 STS. 开头 |
SecretAccessKey |
临时 SK |
SecurityToken |
安全 Token,必须带在请求头里 |
Expiration |
过期时间,格式像 2023-10-27T10:00:00Z |
关键点:用 STS 凭证请求时,必须在请求头里加 x-amz-security-token,否则依然 403。
三、 如何在青云 QOS 上获取 STS 临时凭证?
青云提供了 API 来生成 STS 凭证。你可以通过两种主流方式:
方式一:调用青云 STS API(推荐用于后端生成)
青云的 STS API 和阿里云/腾讯云的格式类似,但具体 endpoint 要看你用的云服务商文档。假设我们使用通用的 S3 兼容签名流程,后端调用青云 STS 服务拿到临时密钥。
注意:不同云厂商的 STS 接口略有差异。青云(QingCloud)的 STS 接口通常是通过 青云控制台 或 API 调用
CreateSessionToken或类似接口。下面我以 Python 为例,展示如何调用一个典型的 STS 服务(这里以通用 AWS STS 风格模拟,实际青云调用请参照其最新 API 文档,但逻辑一致)。
import boto3
from botocore.config import Config
# 青云 QOS 兼容 S3,所以可以用 boto3,但需要指定 endpoint
qos_config = Config(
region_name='us-east-1', # 根据你的 QOS 区域调整
signature_version='s3v4'
)
# 使用永久密钥初始化 STS 客户端
sts_client = boto3.client(
'sts',
aws_access_key_id='YOUR_PERMANENT_AK',
aws_secret_access_key='YOUR_PERMANENT_SK',
config=qos_config,
endpoint_url='https://sts.your-qingcloud-endpoint.com' # 替换为青云实际的 STS endpoint
)
# 获取临时凭证,假设有效期 1 小时
response = sts_client.get_session_token(
DurationSeconds=3600
)
credentials = response['Credentials']
print("临时 AK:", credentials['AccessKeyId'])
print("临时 SK:", credentials['SecretAccessKey'])
print("Token:", credentials['SecurityToken'])
print("过期时间:", credentials['Expiration'])
重要:青云的 STS 具体 endpoint 你需要去青云控制台查看,或者查阅其 API 文档。以上代码框架是正确的,只要 endpoint 对,就能拿到临时凭证。
方式二:通过青云控制台手动生成(适合测试)
如果你只是想测试,可以去青云控制台,找到 访问控制(IAM) 或 对象存储 相关页面,通常有“创建临时凭证”或“STS 令牌生成”的功能。手动生成后,你会得到 AK、SK 和 Token。
四、 前端如何使用 STS 凭证上传文件?
拿到临时凭证后,前端需要把它用在 S3 SDK 或者直接发请求。这里用 AWS SDK (JavaScript) 为例,因为它最通用,青云 QOS 兼容 S3。
// 前端代码示例:使用 STS 凭证上传文件到 QOS
const AWS = require('aws-sdk');
// 从后端接口获取临时凭证
const tempCredentials = {
accessKeyId: 'STS.AKIAIOSFODNN7EXAMPLE', // 临时 AK
secretAccessKey: '临时SK', // 临时 SK
sessionToken: '临时Token' // 安全 Token,必须!
};
// 初始化 S3 客户端,指向青云 QOS 的 endpoint
const s3 = new AWS.S3({
accessKeyId: tempCredentials.accessKeyId,
secretAccessKey: tempCredentials.secretAccessKey,
sessionToken: tempCredentials.sessionToken, // 关键:带上 Token
endpoint: 'https://s3.your-qingcloud-endpoint.com', // 青云 QOS endpoint
region: 'us-east-1',
signatureVersion: 'v4'
});
// 上传文件
const params = {
Bucket: 'my-test-bucket',
Key: 'uploads/photo.jpg',
Body: fileObject, // 前端获取的 File 对象
ContentType: 'image/jpeg'
};
s3.putObject(params, (err, data) => {
if (err) {
console.error('上传失败:', err);
// 常见错误:403 Forbidden
// 检查点:
// 1. sessionToken 是否带上?
// 2. 凭证是否过期?(err message 里会写 ExpiredToken)
// 3. Bucket 权限是否允许该用户写?
} else {
console.log('上传成功:', data.Location);
}
});
代码要点:
sessionToken必须赋值,否则 QOS 会忽略 Token,用永久密钥逻辑去验证,导致 403。endpoint要换成青云 QOS 的实际地址。signatureVersion: 'v4'确保使用最新的签名算法。
五、 403 签名失效常见问题排查清单
当你遇到 403 时,按以下顺序检查,能解决 90% 的问题:
1. 检查凭证是否过期
现象:错误信息里明确说 ExpiredToken 或 Token is expired。
解决:
- 前端拿到凭证后,先展示过期时间。
- 在凭证过期前(比如剩 5 分钟),调用后端接口刷新新的 STS 凭证。
- 后端要有一个接口专门给前端“换新鲜”的 Token。
// 简单的时间检查逻辑
const expiresIn = new Date(credentials.Expiration) - new Date();
if (expiresIn < 5 * 60 * 1000) { // 少于 5 分钟
// 调用后端刷新接口
refreshSTS();
}
2. 检查 Security Token 是否遗漏
现象:凭证没过期,但还是 403。
解决:
- 确认前端请求头里有没有
x-amz-security-token。 - 如果用 SDK,确认是否传了
sessionToken。 - 如果用 REST API 直接调,确认 Header 里有没有
x-amz-security-token。
3. 检查签名时间戳
现象:错误信息说 RequestTimeTooSkewed。
解决:
- 确保客户端(前端或后端)的系统时间是准确的,最好用 NTP 同步。
- 时区错误也会导致问题,确保时间戳是 UTC 格式。
4. 检查 Bucket 策略和权限
现象:签名正确,但还是 403。错误信息可能是 AccessDenied。
解决:
- 去青云控制台,检查该 Bucket 的 访问控制策略。
- 确认 STS 用户(临时凭证对应的用户)有
s3:PutObject、s3:GetObject等权限。 - 注意:青云的 IAM 策略里,动作名称可能和 AWS 略有不同,请以青云文档为准。
示例策略(JSON 格式,用于青云 IAM):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"STS": "临时凭证对应的用户ARN"
},
"Action": [
"qcs:cos:PUT",
"qcs:cos:GET",
"qcs:cos:POST"
],
"Resource": "qcs::cos:ap-shanghai:uid/10001001:bucket/my-test-bucket/*"
}
]
}
注意:青云的 IAM 策略格式可能基于其自家标准,上面是参考 AWS 风格,具体字段
qcs:cos:PUT等请查阅青云最新文档。
5. 检查签名算法和区域
现象:一直 403,但凭证和权限都没问题。
解决:
- 确认签名版本是
AWS4-HMAC-SHA256(即 v4)。 - 确认请求的
region和endpoint一致。比如,你用的是ap-shanghai区域,但 endpoint 写成了us-east-1,签名会错。
六、 后端如何正确生成签名(进阶)
有时候,不是前端的问题,而是后端生成的预签名 URL(Presigned URL)有问题。预签名 URL 是后端生成一个带签名的 URL,前端直接用它上传,不用暴露密钥。
生成预签名 URL 的正确姿势(Python + boto3)
import boto3
from botocore.config import Config
# 配置
s3_config = Config(
signature_version='s3v4',
region_name='ap-shanghai' # 根据实际区域
)
s3_client = boto3.client(
's3',
aws_access_key_id='YOUR_PERMANENT_AK',
aws_secret_access_key='YOUR_PERMANENT_SK',
config=s3_config,
endpoint_url='https://cos.ap-shanghai.myqcloud.com' # 青云 QOS 的 S3 兼容 endpoint
)
# 生成预签名 URL,有效期 15 分钟
url = s3_client.generate_presigned_url(
ClientMethod='put_object',
Params={
'Bucket': 'my-test-bucket',
'Key': 'uploads/photo.jpg',
'ContentType': 'image/jpeg'
},
ExpiresIn=900, # 秒
HttpMethod='PUT'
)
print("预签名 URL:", url)
注意:青云 QOS 的 endpoint 格式通常是 https://cos.<region>.myqcloud.com 或类似,务必去控制台确认。
七、 给小朋友的比喻:为什么这么麻烦?
想象你要去一个高级俱乐部(对象存储)送一个包裹(上传文件)。
- 永久 AK/SK 就像你的身份证和家门钥匙,给了别人你就惨了。
- STS 临时凭证 就像俱乐部给你发的 临时访客证,上面有名字、照片,还有 过期时间(比如下午 5 点失效)。到了 5 点,这张证就作废了,保安(服务器)就不认了。
- Security Token 就是访客证上的 防伪芯片,保安要用专用设备扫一下,确认是真的。如果你只给保安看身份证(AK/SK),没带访客证(Token),保安会说:“你身份是真的,但没权限进这个区域!”——这就是 403。
- 签名 就是你写的一封 加密信,告诉保安:“这包裹是我的,我没篡改过。” 如果信上的印章(签名)和保安手里的印模对不上,或者信件打开的时间(时间戳)已经过了有效期,保安也会拒绝。
所以,403 就是保安在说:“要么你证件过期了,要么你没带防伪芯片,要么你的信盖章不对。”
八、 总结与建议
- 永远不要用永久 AK/SK 给前端。改用 STS 临时凭证,安全又省心。
- 检查
sessionToken。这是 STS 场景 403 的头号杀手。 - 关注过期时间。前端要定期刷新凭证,或者生成预签名 URL 时设置足够短的有效期。
- 核对区域和 Endpoint。签名是包含区域信息的,填错了签名必错。
- 查看错误信息。QOS 的 403 错误信息通常会告诉你具体原因(
ExpiredToken、SignatureDoesNotMatch、AccessDenied),根据提示排查。
希望这篇指南能帮你彻底解决青云对象存储签名失效的问题!如果还有具体报错信息,欢迎贴出来,我可以帮你进一步分析。
