You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django上传静态文件到Amazon S3时遭遇403 Forbidden错误求助

Django静态文件上传S3出现403 Forbidden错误的解决方法

问题背景

尝试通过boto3和django-storages模块将Django项目的静态文件上传至Amazon S3,执行python manage.py collectstatic命令时触发403 Forbidden错误,具体报错出现在HeadObject操作环节。

现有配置

settings.py中的AWS配置

AWS_ACCESS_KEY_ID = "<public-key>"
AWS_SECRET_ACCESS_KEY_ID = "<private-key>"

AWS_STORAGE_BUCKET_NAME = "<bucket-name>"

STORAGES  = {
    # Media files (images) management
    "default": {
        "BACKEND": "storages.backends.s3boto3.S3StaticStorage",
    },
    
    # CSS and JS file management
    "staticfiles": {
        "BACKEND": "storages.backends.s3boto3.S3StaticStorage",
    },
}

AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'

AWS_S3_FILE_OVERWRITE = False

IAM用户权限

已为IAM用户授予AmazonS3FullAccess权限策略。

S3存储桶策略

{
    "Version": "2012-10-17",
    "Id": "Policy1706347165594",
    "Statement": [
        {
            "Sid": "Stmt1706347164281",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::<bucket-name>/*"
        }
    ]
}

完整报错信息

You have requested to collect static files at the destination location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes
Traceback (most recent call last):
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/manage.py", line 22, in <module>
    main()
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/core/management/base.py", line 458, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 209, in handle
    collected = self.collect()
                ^^^^^^^^^^^^^^
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 135, in collect
    handler(path, prefixed_path, storage)
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 368, in copy_file
    if not self.delete_file(path, prefixed_path, source_storage):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 278, in delete_file
    if self.storage.exists(prefixed_path):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/storages/backends/s3.py", line 514, in exists
    self.connection.meta.client.head_object(Bucket=self.bucket_name, Key=name)
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/botocore/client.py", line 553, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/gajeet/Documents/Django/Django_ultimate/dev2/.venv/lib/python3.11/site-packages/botocore/client.py", line 1009, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (403) when calling the HeadObject operation: Forbidden

解决步骤

1. 修正settings.py中的密钥配置错误

settings里的密钥配置项写错了,AWS_SECRET_ACCESS_KEY_ID应改为AWS_SECRET_ACCESS_KEY,去掉末尾的_ID:

AWS_ACCESS_KEY_ID = "<public-key>"
AWS_SECRET_ACCESS_KEY = "<private-key>"  # 修正此处

2. 扩展存储桶策略的资源范围

当前存储桶策略仅授权了桶内对象的权限,HeadObject操作需要对桶本身的访问权限。更新策略,添加桶级资源:

{
    "Version": "2012-10-17",
    "Id": "Policy1706347165594",
    "Statement": [
        {
            "Sid": "Stmt1706347164281",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::<bucket-name>",
                "arn:aws:s3:::<bucket-name>/*"
            ]
        }
    ]
}

3. 验证IAM用户权限有效性

虽然已授予AmazonS3FullAccess,需确认该策略已正确附加到IAM用户,且访问密钥未过期或禁用。可通过AWS CLI测试:

aws s3 head-object --bucket <bucket-name> --key test-key

若返回正常则权限没问题,若仍报403需检查IAM用户的权限边界或是否存在其他拒绝策略。

4. 检查存储桶的Block Public Access设置

若存储桶开启了Block Public Access,会覆盖存储桶策略的公开权限。进入S3控制台,在对应桶的Permissions栏下查看Block Public Access,根据需求调整选项(若静态文件需要公开访问,需关闭相关阻止项)。

5. 修正STORAGES的后端配置(可选)

媒体文件应使用S3Boto3Storage而非S3StaticStorage,静态文件用S3StaticStorage是正确的,调整默认存储后端:

STORAGES  = {
    # Media files (images) management
    "default": {
        "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",  # 修正此处
    },
    
    # CSS and JS file management
    "staticfiles": {
        "BACKEND": "storages.backends.s3boto3.S3StaticStorage",
    },
}

内容的提问来源于stack exchange,提问作者G Ajeet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:07:33