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'
]
然后在前端call这个url就实现了下载文件
最后推荐单独写在一个应用里