仅S3用户专用的Django DRF映像存储

时间:2018-12-19 10:36:48

标签: django heroku amazon-s3 django-rest-framework

我正在使用DRF将用户上传的图像存储到S3,在S3中,我可以看到可以使用URL公开访问图像。

我对此的担心是,有什么最好的方法来保护此图像并将其限制为该图像的所有者,以供查看。

我正在使用Heroku部署我的DRF API框架,但是对于将图像文件上传到S3存储桶的用户,这是我的安全问题。

我正在尝试通过用户名称来隔离用户图像。但是它仍然是公开的,因此我可以为另一个用户访问此图像,只需找出它的名称即可。

这里是媒体图片的S3 URL

https://xxx.s3.amazonaws.com/media/persons/niravjoshi/20181218152410.jpg

这是我的 settings.py for Django

import os
import pymysql  # noqa: 402
pymysql.install_as_MySQLdb()
import dj_database_url
from decouple import config
import django_heroku

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

#SECRET_KEY = 'feufm)u(pvsvb%&_%%*)p_bpa+sv8zt$#_-do5q3(vou-j*d#p'

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL')
    )
}

DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    #Django Project Apps
    'persons',
    'rest_framework',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
    #'social_django',
]

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
AWS_REGION = os.environ.get('AWS_REGION', '')  # e.g. eu-west-1
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY', '')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_KEY', '')
AWS_STORAGE_BUCKET_NAME = os.environ.get('S3_BUCKET', '')
AWS_QUERYSTRING_AUTH = False
AWS_S3_CUSTOM_DOMAIN = os.environ.get("AWS_S3_CUSTOM_DOMAIN", "")

MEDIAFILES_LOCATION = 'media'
DEFAULT_FILE_STORAGE = 'DjangoE2ISAapi.storage_backends.MediaStorage'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'DjangoE2ISAapi.storage_backends.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_STATIC_LOCATION)




STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
django_heroku.settings(locals())

from DjangoE2ISAapi.restconf.main import *

这是我的storage_backends.py

from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage

class StaticStorage(S3Boto3Storage):
    location = settings.AWS_STATIC_LOCATION

class MediaStorage(S3Boto3Storage):
    location = settings.MEDIAFILES_LOCATION

这是我的Person模型。

from django.core.serializers import serialize
from django.db import models
from django.conf import settings
import json
from django.core.serializers.json import DjangoJSONEncoder
# Create your models here.

def upload_file(instance,filename):
    import os
    from django.utils.timezone import now
    filename_base, filename_ext = os.path.splitext(filename)
    return "persons/{user}/{filename}".format(user=instance.UserName, filename=now().strftime("%Y%m%d%H%M%S")+filename_ext.lower())



class PersonQuerySet(models.QuerySet):
    def serialize(self):
        list_values=list(self.values('UserName','PersonId','PersonName','Person_Image','Person_sex','Person_BDate'))
        print (list_values)
        return json.dumps(list_values,sort_keys=True,indent=1,cls=DjangoJSONEncoder)

class PersonManager(models.Manager):
        def get_queryset(self):
            return PersonQuerySet(self.model,using=self._db)


class Person(models.Model):
    UserName = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)
    PersonId = models.AutoField(primary_key=True)
    PersonName = models.CharField("person's first name", max_length=30,null=False)
    Person_Image = models.ImageField(upload_to=upload_file,null=True, blank=True)
    SEX = (('M','Male'),('F','Female'), ('N','None'), )
    Person_sex = models.CharField(max_length=1,choices=SEX,null=False)
    Person_BDate = models.DateField(null=False)
    Person_CDate =  models.DateField(null=False,auto_now_add=True)
    objects = PersonManager()


    def __str__(self):
        return str(self.PersonName) or ""

    def serialize(self):
        data={
            'UserName': self.UserName,
            'PersonId': self.PersonId,
            'PersonName': self.PersonName,
            'Person_Image':self.Person_Image,
            'Person_sex': self.Person_sex,
            'Person_Bdate': self.Person_BDate
        }
        data = json.dumps(data,sort_keys=True,indent=1,cls=DjangoJSONEncoder)
        return data

    @property
    def owner(self):
        return self.UserName

enter image description here

以下是Person API视图的响应:

enter image description here

2 个答案:

答案 0 :(得分:1)

boto的ACL的文档为here。我建议仅使用private“固定策略”-由于您的用户仍然没有S3帐户,因此这是最简单的想法。您的应用程序当然必须跟踪哪些用户“拥有”哪些文件(这应该是非常非常简单的Django模型!)。

为了强制用户只能通过自己的应用程序进行下载,只需在生成URL时将一个小值传递给expires_in参数即可。用户只能通过您的应用程序获得有效的下载链接,并且下载后该链接将失效。

以下是用于生成下载链接的代码示例:

@login_required
def download_document(request, file_id):
    '''
     Request handler to download file
    '''
    file = Document.objects.get(pk=file_id)
    s3 = get_aws_s3_client() #function to create s3 session
    download_url = s3.generate_presigned_url(
        'get_object',
        Params= {'Bucket': file.bucket_name, 'Key': file.key},
        ExpiresIn=5, #the url won't be valid after only 5 seconds
    )
    return redirect(download_url)

您可以通过添加以下代码进一步使该视图仅对文件所有者有效:

if file.owner == request.user : 
   return redirect(download_url)
else :
   # render 403.html  since access denied. 

编辑: 根据要求,此解决方案需要使用特定的模型来存储与每个文档有关的信息。 该模型将类似于以下内容:

class Image(models.Model):
    customer     = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE,)
    key          = models.CharField(max_length=120) #uuid64 will be stored here and used for s3 urls
    name         = models.CharField(max_length=120, null=True, blank=True)
    size         = models.FloatField()
    human_size   = models.CharField(max_length=120, null=True, blank=True)
    filetype     = models.CharField(max_length=120, null=True, blank=True)
    fextension   = models.CharField(max_length=30, null=True, blank=True)
    bucket_name  = models.CharField(max_length=120, null=True, blank=True)
    region       = models.CharField(max_length=120, null=True, blank=True)
    s3link       = models.CharField(max_length=170, null=True, blank=True)
    timestamp    = models.DateTimeField(auto_now_add=True)
    updated      = models.DateTimeField(auto_now=True)
    uploaded     = models.BooleanField(default=False)
    active       = models.BooleanField(default=True)

    def __str__(self):
        return self.name

由于我从未使用过DRF,因此无法讨论与序列化有关的部分。

答案 1 :(得分:0)

I would add uuid field to every user like.

import uuid

class Person(models.Model):
    uuid = models.UUID(default=uuid.uuid4)
    ....

You can set it as primary key as well instead of AutoField.

and put that unique uuid into URL instead of name in order to look like:

https://xxx.s3.amazonaws.com/media/persons/b3810ec3-dd9d-4f11-a1e1-47835c0058ec/20181218152410.jpg

That image will be still public, but it's impossible to access image if you don't know uuid of particular user.

If you want more secure solution not depending on URL only you need to add some Authentication logic.