5.1 KiB
5.1 KiB
修复 403 Forbidden 错误
问题分析
之前的修复方案有误,导致新的问题:
错误的方案(已修复)
// ❌ 错误:简单替换 URL 会破坏 AWS 签名
const url = await getSignedUrl(this.client, command);
return url.replace('host.docker.internal:9002', '192.168.1.83:9002');
问题: AWS 签名是基于完整 URL 计算的,替换域名后签名失效,导致 403 Forbidden。
正确的方案(已实施)
创建两个 S3Client:
client: 使用S3_ENDPOINT(内部地址) - 用于实际的 S3 操作presignClient: 使用S3_PUBLIC_DOMAIN(公网地址) - 用于生成预签名 URL
// ✅ 正确:使用不同的 client 生成预签名 URL
export class S3 {
private readonly client: S3Client; // 用于内部操作
private readonly presignClient: S3Client; // 用于生成预签名 URL
constructor() {
// client 使用 S3_ENDPOINT (host.docker.internal:9002)
this.client = new S3Client({
...config,
endpoint: fileEnv.S3_ENDPOINT,
});
// presignClient 使用 S3_PUBLIC_DOMAIN (192.168.1.83:9002)
this.presignClient = new S3Client({
...config,
endpoint: fileEnv.S3_PUBLIC_DOMAIN || fileEnv.S3_ENDPOINT,
});
}
async createPreSignedUrl(key: string): Promise<string> {
// 使用 presignClient 生成 URL
return getSignedUrl(this.presignClient, command, { expiresIn: 3600 });
}
}
修复内容
修改的文件
- ✅
src/server/modules/S3/index.ts- 添加
presignClient属性 - 修改构造函数,创建两个客户端
- 修改
createPreSignedUrl使用presignClient - 修改
createPreSignedUrlForPreview使用presignClient
- 添加
工作原理
内部操作流程:
Container → client (S3_ENDPOINT) → host.docker.internal:9002 → MinIO ✅
预签名 URL 流程:
Container → presignClient (S3_PUBLIC_DOMAIN) → 生成 URL: http://192.168.1.83:9002/...
→ 返回给浏览器
Browser → http://192.168.1.83:9002/... → MinIO ✅
立即应用
方法 1: 一键修复
./rebuild-and-restart.sh
方法 2: 手动执行
# 1. 停止容器
docker-compose -f docker-compose.lingjingwanxiang.yml down
# 2. 重新构建(重要!)
docker build -t lingjingwanxiang:latest .
# 3. 启动容器
docker-compose -f docker-compose.lingjingwanxiang.yml up -d
# 4. 查看日志
docker logs -f lingjingwanxiang
验证修复
1. 检查容器状态
docker ps | grep lingjingwanxiang
# 应该看到两个容器运行中
2. 测试上传
- 访问 http://192.168.1.83:3210
- 打开开发者工具 (F12) → Network 标签
- 上传一个文件
- 查看 PUT 请求:
- Request URL 应该是:
http://192.168.1.83:9002/lingjingwanxiang/files/xxx - Status 应该是:
200 OK✅
- Request URL 应该是:
3. 验证预签名 URL
在 Network 标签中找到 createS3PreSignedUrl 请求,查看返回的 URL:
期望结果:
http://192.168.1.83:9002/lingjingwanxiang/files/12345/xxx.jpg?X-Amz-Algorithm=...
不应该包含 host.docker.internal
故障排查
如果仍然 403
-
检查环境变量:
docker exec lingjingwanxiang printenv | grep S3确保有:
S3_PUBLIC_DOMAIN=http://192.168.1.83:9002 -
检查 MinIO:
# MinIO 是否运行 docker ps | grep minio # 测试访问 curl http://192.168.1.83:9002/minio/health/live -
查看容器日志:
docker logs --tail 50 lingjingwanxiang 2>&1 | grep -i error
如果返回其他错误
| 错误 | 可能原因 | 解决方法 |
|---|---|---|
| 403 Forbidden | 签名问题或权限问题 | 确保重新构建了镜像 |
| Network Error | MinIO 未运行 | docker-compose -f docker-compose.minio.yml up -d |
| CORS Error | CORS 配置问题 | 运行 ./setup-minio.sh |
| 404 Not Found | Bucket 不存在 | 运行 ./setup-minio.sh |
技术细节
为什么需要两个 Client?
AWS SDK 在生成预签名 URL 时,会使用创建 S3Client 时指定的 endpoint 来构建 URL,并基于这个 URL 计算签名。
- 如果使用内部地址 (
host.docker.internal:9002),浏览器无法访问 - 如果全部使用公网地址 (
192.168.1.83:9002),容器内部操作会失败
因此需要两个客户端,各司其职。
AWS 签名机制
AWS Signature V4 签名包括:
- HTTP 方法 (PUT, GET, etc.)
- 完整的 URL (包括 host)
- Headers
- Payload
任何修改都会导致签名失效。
相关文档
修复日期: 2025-11-28 修复版本: v2 状态: ✅ 已修复,待测试