【问题标题】:amazon s3 and django - Allow only the users from my website and not the anonymous usersamazon s3 和 django - 只允许来自我网站的用户而不是匿名用户
【发布时间】:2026-02-03 01:00:02
【问题描述】:

我正在使用 amazon s3 存储上传的用户图像。我的问题是:

  • 如果我允许或受赠人,我将无法上传或下载内容。
  • 如果我允许或授予所有人,所有用户和(尤其是)匿名用户都将能够看到我不希望的内容。

所以,我的问题是,我该怎么做才能只有我网站上的用户才能上传、下载和删除内容?

因为我有条件:

  1. 仅关注用户的用户(user1、user2、user3...) (user0) 可以下载/查看内容吗?
  2. 只有上传视图的用户才能删除内容。

models.py:

def get_upload_file_name(instance, filename):
    return "uploaded_files/%s_%s" %(str(time()).replace('.','_'), filename)

PRIVACY = (
    ('H','Hide'),
    ('F','Followers'),
    ('A','All'),
)

class Status(models.Model):
    body = models.TextField(max_length=200)
    image = models.ImageField(blank=True, null=True, upload_to=get_upload_file_name)
    privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
    user = models.ForeignKey(User)

settings.py:

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'

AWS_ACCESS_KEY_ID = 'FAKEAMAZONKEY'

AWS_SECRET_ACCESS_KEY = 'FAKEAMAZONSECRETKEY'

AWS_STORAGE_BUCKET_NAME = 'fakebucketname'

更新

用户关系模型

class Person(models.Model):
    user = models.OneToOneField(User)
    relationships = models.ManyToManyField('self', through='Relationship', 
                                           symmetrical=False, 
                                           related_name='related_to')

    def __unicode__(self):
        return self.user.username

    def add_relationship(self, person, status):
        relationship, created = Relationship.objects.get_or_create(
            from_person=self,
            to_person=person,
            status=status)
        return relationship

    def remove_relationship(self, person, status):
        Relationship.objects.filter(
            from_person=self, 
            to_person=person,
            status=status).delete()
        return

    def get_relationships(self, status):
        return self.relationships.filter(
            to_people__status=status, 
            to_people__from_person=self)

    def get_related_to(self, status):
        return self.related_to.filter(
            from_people__status=status, 
            from_people__to_person=self)

    def get_following(self):
        return self.get_relationships(RELATIONSHIP_FOLLOWING)

    def get_followers(self):
        return self.get_related_to(RELATIONSHIP_FOLLOWING)

    def get_friends(self):
        return self.relationships.filter(
            to_people__status=RELATIONSHIP_FOLLOWING, 
            to_people__from_person=self,
            from_people__status=RELATIONSHIP_FOLLOWING, 
            from_people__to_person=self)


RELATIONSHIP_FOLLOWING = 1
RELATIONSHIP_BLOCKED = 2
RELATIONSHIP_STATUSES = (
    (RELATIONSHIP_FOLLOWING, 'Following'),
    (RELATIONSHIP_BLOCKED, 'Blocked'),
)

class Relationship(models.Model):
    from_person = models.ForeignKey(Person, related_name='from_people')
    to_person = models.ForeignKey(Person, related_name='to_people')
    status = models.IntegerField(choices=RELATIONSHIP_STATUSES)

    def __unicode__(self):
        return "%s %s %s" % (self.from_person, self.get_status_display(), self.to_person)


class Activity(models.Model):
    actor = models.ForeignKey(User)
    action = models.CharField(max_length=100)
    content_type = models.ForeignKey(ContentType, related_name="content_type")
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
    element_type = models.ForeignKey(ContentType, related_name="element_type", blank=True, null=True)
    element_id = models.PositiveIntegerField(blank=True, null=True)
    element_object = generic.GenericForeignKey('element_type', 'element_id')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)

    class Meta:
        verbose_name = 'Activity'
        verbose_name_plural = 'Activities'
        ordering = ['-pub_date']

    def __unicode__(self):
        return ("%s %s") % (self.actor.username, self.action)

    def get_rendered_html(self, user=None):
        if self.element_type:
            template_name = '%s_activity.html' %(self.element_type.name)
        else:
            template_name = '%s_activity.html' %(self.content_type.name)

        return render_to_string(template_name, {
            'object':self.content_object,
            'actor':self.actor,
            'action':self.action,
            'element_object':self.element_object,
            'user':user,
            'pub_date':self.pub_date
            })

【问题讨论】:

  • 您需要将您的 s3 权限更改为仅允许从您的服务器下载,并且后端 webapp 需要过滤掉可以向 s3 文件发出请求的人。我正在使用 symfony,所以帮不上什么忙。如果您不对 s3 添加限制,那么任何知道该链接的人都可以从 s3 下载该文件,这就是数据泄露。 Look at bucket policies
  • @George 是的,这就是我想要的。我已将您的评论添加为有用。我希望有人能帮助我。
  • 我当然希望那些不是你真正的钥匙
  • 它们不是...我做的第一件事是检查这些键是否有效。你可以想象有机器人在每个网站上抓取这样的密钥……亚马逊密钥特别容易抓取。

标签: django amazon-web-services amazon-s3 boto


【解决方案1】:

直接加载文件时,您可以使用Amazon's Query String Authentication,您必须在 URL 中包含签名以验证您是否可以获取文件。 您的应用可以使用 boto 的 Key.generate_url method 创建这样的 url。您还应该添加一个过期时间,在此之后链接将不再有效。

编辑:更详细的description 关于如何使用 boto,,,, 进行设置

【讨论】:

    【解决方案2】:

    既然 boto3 出来了,我们可以使用: https://github.com/boto/boto3/blob/develop/docs/source/guide/s3-presigned-urls.rst

    import boto3
    s3_client = boto3.client('s3')
    try:
        response = s3_client.generate_presigned_url(ClientMethod=client_method_name,
                                                    Params=method_parameters,
                                                    ExpiresIn=expiration,
                                                    HttpMethod=http_method)
    except ClientError as e:
        logging.error(e)
        return None
    # The response contains the presigned URL
    return response
    

    我们也可以使用类似的方法在同一个引用链接中上传对象。

    【讨论】:

    • 如果我理解正确,用户可以分享这个预签名的 url 并绕过安全性
    【解决方案3】:

    正如 George 所说,您需要采取 2 个步骤来完成这项工作,尽管我建议您只授予对特定密钥的访问权限,而不是授予对 ec2 实例的访问权限(从未使用过该方法)(已广泛使用此方法)

    1. 允许您希望的用户完全控制存储桶资源。

    这里是关于权限如何工作的亚马逊。它们并非微不足道,因此您需要了解主要账户是谁,以及您是使用其密钥还是 IAM 用户的密钥。无论如何,您的密钥应该具有对资源的完全访问权限(希望这是您的存储桶,而不是其他人的存储桶,因为事情会更复杂一些) http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-access-control.html

    1. 实现业务逻辑以:
      • 允许您的应用用户创建 S3 文件
      • 允许关注用户 X 的用户查看/下载用户 X 创建的文件
      • 只允许用户 X 删除他创建的文件

    创建资源: - 如果您提供的后端工作正常,并且您已经很好地配置了您的 S3 权限,那么您不应该做任何事情。但是,要检查您的密钥是否存在问题,请执行以下操作:

    from boto.s3.connection import S3Connection as s3c
    connection = s3c('your-aws-access-key', 'your-aws-secret-key')
    bucket = connection.get_bucket('your-bucket-name')
    
    new_key = bucket.new_key('your-key-name')  #meaning the name of the file
    # https://github.com/boto/boto/blob/develop/boto/s3/bucket.py#L611
    
    new_key.send_file(file_object)  #this will upload the file
    # https://github.com/boto/boto/blob/develop/boto/s3/key.py#L709
    

    如果一切正常,您应该能够在 S3 浏览器中看到您的文件 - 如果没有,您将不得不返回有关 S3 访问权限的文档。

    只允许关注用户 X 的用户访问用户上传的文件: 您的models.py 文件没有说明您是如何实现follow 逻辑的。我分不清谁在跟踪谁。你有自己的自定义User 模型吗?你使用 Django 的但有扩展名吗?您有自己的模型来链接用户吗?分享更多关于一个用户如何“关注”另一个用户的信息,然后我什至可以分享代码片段。

    要允许用户 X 删除自己的文件: 我猜测 Status.user 字段已经包含对创建图像的用户的引用。如果是这样:把它放在你的视图中:

    def picture_deletion_view(request, status_identifier ...):
        try:
            status = Status.objects.filter(id_or_some_identifier=status_identifier)
        except Status.DoesNotExist:
            return SomeHttpResponse()
    
        if request.user.id == status.user.id:
            # you can delete the picture and redirect... to somewhere
        else:
            # you can't delete! redirect...or something
    

    【讨论】:

    • 我添加了关系的逻辑。请看一看。谢谢。
    【解决方案4】:

    这是由视图和模板处理的逻辑。 例如,在模板中,您可以使用带有上传表单的 {% if user.is_authenticated %} 这样的块,并且在视图中您还可以检查用户是否已通过身份验证,然后才将您的内容加载到 s3

    【讨论】:

    • 是的,但我有完全不同的问题。假设某人拥有您的图片的链接(这很容易获得),即使他/她不是用户,他/她也可以轻松查看该图片。
    • 您可以在 amazon IAM 部分创建一个用户,然后设置AWS_STORAGE_BUCKET_NAMEAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY 上传到您的存储桶。我认为您还可以将亚马逊 CORS 设置设置为仅允许来自您的域的请求
    【解决方案5】:

    所以我不能和 aws.s3 说话,但是对于 django,限制谁可以访问视图的最佳方法是要求通过 django 的 login_required 装饰器登录:

    from django.contrib.auth.decorators import login_required
    
    @login_required
    def my_view(request):
        ...
    

    或者,如果您想公开所有视图,而不是页面的一部分(链接等),您可以将该信息放入模板中,正如@t_io 所说,但 django 文档强烈建议不要将模板中有很多处理逻辑,因为它会减慢网站的速度。最好将这些信息放在视图中:

    @login_required
    def image_view(request):
        user = request.user
    
        # this list has the user's own images
        mine = []
        for status in user.status_set.all():
            mine.append(status.image)
    
        # this list has the images the user can see (relationship-based)
        following = []
        friends = []
    
        # you can get the person from the user
        person = user.person
    
        for status in person.get_friends().all():
            friends.append(status.image)
    
        for status in person.get_following().all():
            following.append(status.image)
    
        ctx = dict(user=request.user, friends=friends, following=following, mine=mine)
        return render("template.html", ctx)
    

    在模板中,您可以遍历列表

    {% for img in mine %}
        <li><a href=...></a></li>
    {% endfor %}
    
    {% for img in following %}
        <li><a href=...></a></li>
    {% endfor %}
    

    ...你明白了

    为了防止人们直接导航到媒体 url,您可以使用 sendfile(python 库)或 apache/nginx 等效项。所以你需要另一个视图:

    import sendfile
    from django.conf import settings
    @login_required
    def handle_media(request, path=None):
        try:
            requested_file = os.path.join(settings.MEDIA_ROOT, path)
            return sendfile.sendfile(request, requested_file)
        except:
            pass
    
        return HttpResponseNotFound('<h1>Page not found {}</h1>'.format(path))
    

    然后你需要一个额外的 url (urls.py):

    urlpatterns = patterns('',
        url(r'/media/(?P<path>[a-zA-Z0-9_- /]+)$', views.handle_media),
    )
    

    请参阅django-sendfile 以在 sendfile 上使用

    【讨论】:

    • 是的,这不是一种方式。但问题是,如果您获得图片的链接,只需转到该网址即可轻松获得...