Django结合AWS S3实现仅所有者可见的私有媒体文件访问控制咨询
Django结合AWS S3实现私有媒体文件的用户权限控制方案
前提配置:关闭S3桶公共访问权限
首先需要确保S3存储桶没有公共访问权限,这是所有权限控制的基础:
- 进入AWS S3控制台,找到对应存储桶,打开「权限」选项卡
- 开启「阻止公共访问」的全部4项配置
- 删除存储桶策略中所有允许公共访问的规则
- 配置文件上传默认ACL为
private,禁止对象公开访问
核心实现方案:增加Django权限校验代理层
不要直接返回S3原始链接,改为返回Django服务的代理路由地址,由Django先校验用户权限,再返回文件内容或者短时有效的预签名链接。
第一步:新增私有文件代理路由
在对应app的urls.py中添加路由:
from django.urls import path from .views import private_file_proxy urlpatterns = [ path("private-files/<int:file_id>/image/", private_file_proxy, name="private_file_proxy"), ]
第二步:实现代理校验逻辑(两种可选方案)
首先确保你已经安装boto3并在Django配置中添加了AWS密钥、区域、存储桶名等参数。
方案1:校验通过后返回短时S3预签名URL(推荐,性能更好)
该方案不会占用Django服务的带宽,适合大文件场景,预签名URL过期后无法再访问:
import boto3 from django.conf import settings from django.http import HttpResponseRedirect, Http404, HttpResponse from django.contrib.auth.decorators import login_required from .models import PrivateFile s3_client = boto3.client( 's3', aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, region_name=settings.AWS_S3_REGION_NAME ) @login_required def private_file_proxy(request, file_id): # 校验文件是否存在 try: file_obj = PrivateFile.objects.get(id=file_id) except PrivateFile.DoesNotExist: raise Http404 # 校验当前用户是否为文件所有者 if file_obj.owner != request.user: return HttpResponse(status=403) # 生成有效期为10分钟的预签名URL,有效期可自行调整,越短安全性越高 presigned_url = s3_client.generate_presigned_url( 'get_object', Params={ 'Bucket': settings.AWS_STORAGE_BUCKET_NAME, 'Key': file_obj.image.name }, ExpiresIn=600 ) return HttpResponseRedirect(presigned_url)
方案2:校验通过后代理返回S3文件内容
该方案不会暴露S3地址,安全性更高,但会占用Django服务的带宽,适合小文件场景:
@login_required def private_file_proxy(request, file_id): try: file_obj = PrivateFile.objects.get(id=file_id) except PrivateFile.DoesNotExist: raise Http404 if file_obj.owner != request.user: return HttpResponse(status=403) # 从S3读取文件内容 s3_response = s3_client.get_object( Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_obj.image.name ) # 构造响应返回给前端 response = HttpResponse( s3_response['Body'].read(), content_type=s3_response['ContentType'] ) # 如需强制下载可添加如下配置 # response['Content-Disposition'] = f'attachment; filename="{file_obj.image.name.split("/")[-1]}"' return response
第三步:修改序列化器返回代理地址
不要直接返回image字段的原生S3地址,改为返回自定义的代理路由地址,DRF序列化器示例如下:
from rest_framework import serializers from .models import PrivateFile from django.urls import reverse class PrivateFileSerializer(serializers.ModelSerializer): image = serializers.SerializerMethodField() class Meta: model = PrivateFile fields = ['id', 'owner', 'image', 'created'] def get_image(self, obj): request = self.context.get('request') relative_url = reverse('private_file_proxy', args=[obj.id]) return request.build_absolute_uri(relative_url)
额外安全优化建议
- 预签名URL的有效期建议控制在1~15分钟之间,避免链接泄露后被长期滥用
- 配置S3桶的CORS规则,仅允许你的业务域名访问
- 对代理接口添加请求频率限制,防止恶意批量爬取文件
- AWS密钥不要硬编码在代码中,通过环境变量或者保密配置服务管理
- 生产环境开启Django的CSRF校验,避免跨站请求伪造攻击
内容的提问来源于stack exchange,提问作者Khan Asfi Reza
相关产品推荐
相关产品推荐

