django安全下载文件

django安全下载文件

现实情况中,我们不能随意的让用户下载文件,要做一些保护措施,例如加密或设置过期时间等等。我们可以利用django自带的加密方法来实现对文件的下载进行安全限制

我们的app一般分user-portal和operation-portal,那我们需要实现两个端的下载文件的接口

示例代码:

url.py

urlpatterns = [
  # user-portal的下载文件路由
    path('user-portal/<str:secret>/<str:file_name>', media_view.UpDownloadFileView.as_view()),
    path('operation-portal/<str:secret>/<str:file_name>', media_view.OpDownloadFileView.as_view()),
]

view.py

from django.http import FileResponse
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from app01.handle_file import SecureFilePath

# user_portal.py
class UpDownloadFileView(GenericAPIView):
    permission_classes = ()
    authentication_classes = ()

    def get(self, request, *args, **kwargs):
        secret = self.kwargs.get("secret")
        # 解密获取文件路径
        data = SecureFilePath.load(secret=secret)
        if data is None:
            return Response("Expired or happend error")
        file_path = data.get("path")
        if not os.path.exists(file_path):
            return Response("file not exist")
        return FileResponse(open(file_path, "rb"), as_attachment=True)
      
# operation-portal.py
class OpDownloadFileView(GenericAPIView):
    permission_classes = ()
    authentication_classes = ()

    def get(self, request, *args, **kwargs):
        secret = self.kwargs.get("secret")
        data = SecureFilePath.load(secret=secret)
        if data is None:
            return Response("Expired or happend error")
        file_path = data.get("path")
        if not os.path.exists(file_path):
            return Response("file not exist")
        return FileResponse(open(file_path, "rb"), as_attachment=True)

加密类.py(重点)

import os
from django.core import signing


class SecureFilePath:
    EXPIRE_TIME = 5 * 60  # 自定义过期时间
    signer = signing.TimestampSigner()  # django自带的加密

    @staticmethod
    def the_base_url(frontend: str = "OP"):  # 该方法是为了区分up端下载还是op端下载
        return "operation-portal" if frontend == "OP" else "user-portal"

    @classmethod
    def dump(cls, full_path, frontend: str = "OP"):
        # 首先需要获取下载文件接口的地址
        base_url = cls.the_base_url(frontend)
	if not full_file_path:
            return None, None
        file_name = os.path.basename(full_path)
        data = {
            "path": full_path,
            "file_name": file_name
        }
        # 加密
        secret = cls.signer.sign_object(data)
        # 获取路由 返回给前端 让前端call这个路由实现下载文件
        file_url = f"{base_url}/{secret}/{file_name}"
        return file_url, file_name

    @classmethod
    def load(cls, secret):
        try:
          	# 解密
            data = cls.signer.unsign_object(secret, max_age=cls.EXPIRE_TIME)
        except signing.SignatureExpired:  # 过期错误
            return None
        except Exception as e:  # 未知错误
            logger.error(f"path to url load error: msg({e})")
            return None
        return data

接下来就是上传文件

# 上传文件
class UploadFileAPIView(CreateAPIView):
    serializer_class = UploadFileSerializer
    
# 上传文件序列化类
class UploadFileSerializer(serializers.ModelSerializer):
    file_info = serializers.SerializerMethodField()
		# 在这里获取文件的信息(包括下载文件的url以及文件名称)
    def get_file_info(self, instance):
        file_url, file_name = SecureFilePath.dump(full_path=instance.file.path, frontend='UP')
        data = {
            "file_url": file_url,
            "file_name": file_name
        }
        return data

    class Meta:
        model = UploadFile
        fields = [
            'file',
            'file_info'
        ]

image

然后在前端call这个url就实现了下载文件
image
image

最后推荐单独写在一个应用里
image

posted @ 2023-05-12 15:18  zong涵  阅读(73)  评论(0编辑  收藏  举报