Files
ljwx-chat/修复403错误.md
2025-11-29 07:36:44 +08:00

5.1 KiB
Raw Permalink Blame History

修复 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. 测试上传

  1. 访问 http://192.168.1.83:3210
  2. 打开开发者工具 (F12) → Network 标签
  3. 上传一个文件
  4. 查看 PUT 请求:
    • Request URL 应该是: http://192.168.1.83:9002/lingjingwanxiang/files/xxx
    • Status 应该是: 200 OK ✅

3. 验证预签名 URL

在 Network 标签中找到 createS3PreSignedUrl 请求,查看返回的 URL:

期望结果:

http://192.168.1.83:9002/lingjingwanxiang/files/12345/xxx.jpg?X-Amz-Algorithm=...

不应该包含 host.docker.internal

故障排查

如果仍然 403

  1. 检查环境变量:

    docker exec lingjingwanxiang printenv | grep S3
    

    确保有:

    S3_PUBLIC_DOMAIN=http://192.168.1.83:9002
    
  2. 检查 MinIO:

    # MinIO 是否运行
    docker ps | grep minio
    
    # 测试访问
    curl http://192.168.1.83:9002/minio/health/live
    
  3. 查看容器日志:

    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 签名包括:

  1. HTTP 方法 (PUT, GET, etc.)
  2. 完整的 URL (包括 host)
  3. Headers
  4. Payload

任何修改都会导致签名失效。

相关文档


修复日期: 2025-11-28 修复版本: v2 状态: ✅ 已修复,待测试