در پست قبل با استفاده از وب سرور Nginx توانستیم دانلود مستقیم فایل ها را محدود کنیم و توسط جنگو کنترل کنیم که چه کسی بتواند فایل را دانلود کند. اما اگر فایل های ما حجم بالایی داشته باشند و نیاز به استفاده از سرور دیگری برای ذخیره فایلها داشته باشیم چگونه باید از دانلود مستقیم فایلها جلوگیری کنیم؟ در این پست به این موضوع میپردازیم.
در اینجا من از هاست دانلود استفاده میکنم که هزینه مناسبی برای ذخیره سازی فایلها دارد و حجم بالایی را هم در اختیار میگذارد و اکثر این هاست ها اسکریپت PHP را پشتیبانی میکنند بنابراین میتوانیم دانلود فایلها را کنترل و محدود کنیم.
ابتدا در جنگو مدلی میسازیم که بتوانیم مشخصات فایل و آدرس ذخیره آن در هاست را ذخیره کنیم و همچنین همینطور که در مدل من میبینید میتوانید گروه هایی را هم که اجازه دسترسی به این فایل را دارند به مدل اضافه کنید.
class Video(BaseModel): title = models.CharField(max_length=100, null=True, blank=True) address = models.CharField(max_length=255, null=True, blank=True) allowed_memberships = models.ManyToManyField(Group) def __str__(self): return self.title
حالا باید بتوانیم لینک موقتی تولید کنیم که محدودیت هایی برای دانلود و کلاینت دانلود کننده داشته باشد. یکی از محدودیت هایی که اینجا اعمال میکنیم محدود کردن زمان فعال بودن لینک دانلود هست و دیگری اینکه لینک فقط برای آیپی درخواست کننده فعال باشد و در نتیجه کابر نتواند لینک را به شخص دیگری با آیپی متفاوتی دهد.
def visitor_ip_address(request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: ip = request.META.get('REMOTE_ADDR') return ip def hash_md5(request, linkFile, valid_time, is_timestamp=True): path = linkFile secret = env('HASH_SECRET') ip = visitor_ip_address(request) if is_timestamp: t = int(round(time.time() + valid_time)) else: t = valid_time hashed = hashlib.md5() hashed.update(ip.encode('UTF-8')) hashed.update(secret.encode('UTF-8')) hashed.update(path.encode('UTF-8')) hashed.update(str(t).encode('UTF-8')) return hashed.hexdigest(), t, path
خب در کد بالا دوتا فانکشن داریم که اولی برای بدست آوردن آیپی کلاینت است و دومی که فرآیند هش کردن این متغیرهای محدود کننده را انجام میدهد که در نهایت هش تولید شده را میتوانیم به هاست دانلود بفرستیم و در آنجا اعتبار لینک را چک کنیم.
حالا سریالایزری میسازیم تا زمانی که کاربر درخواست دسترسی به فایل را داشت بتوانیم جوابی را که شامل اطلاعات فایل و لینک موقت است را به کاربر بدهیم:
class VideoSerializer(ModelSerializer): dl_link = SerializerMethodField() class Meta: model = Video fields = ('id', 'address', 'title', 'dl_link',) def get_dl_link(self, obj: Video) -> str: request = self.context.get("request") if not request: return "" hashed, t, path = hash_md5(request, obj.address, 3600) return f"{settings.DOWNLOAD_HOST_DOMAIN}/downloads?path={path}&hash={hashed}×tamp={t}"
در اینجا لینک موقت را برای کاربر تولید میکنیم که محدودیت زمانی را هم ۳۶۰۰ ثانیه در نظر گرفتیم.
یک ویو هم میسازیم که کاربر بتونه درخواست دانلود فایل را بدهد:
class GetDLLink(APIView): permission_classes = (AllowAny,) def get(self, request, pk): video = Video.objects.get(id=pk) ser = VideoSerializer(video, context={'request': request}) return Response(ser.data)
در اینجا میتوانید قبل از اینکه لینکی به کاربر بدهید، اگر بررسی ها و محدودیت های دیگری هم نیاز است را انجام دهید، مثلا محدود به کاربر ثبت نام شده با شرط عضویت در فلان گروه باشد.
اینجا کار ما با جنگو تمام میشه و کاربر لینک موقتی داره که میتواند به هاست دانلود درخواست دانلود فایل را بدهد.
حالا در هاست دانلود برای اینکه مسیر مستقیم فایلها قابل حدس نباشه، میتوانیم فایل ها را در دایرکتوری هایی با نام غیرقابل حدس بزاریم چون کاربر آدرس ذخیره فایلها را نمیبیند، همچنین با فایل htaccess هم روی دسترسی به فایلها محدودیت بگذارید تا امنیت کار بالاتر برود.
برای اینکه سورس فایل PHP مقداری طولانی هست میتوانید از این لینک به آن دسترسی داشته باشید. در این فایل پارامترهای لینک موقت ارسال شده را میگیریم و چک میکنیم که لینک معتبر باشد، اگر معتبر بود از مسیر دایرکتوری هایی که فایلها در آن قرار دارند، فایل را برای کاربر قابل دانلود میکنیم.
کار تمام است و حالا محل ذخیره سازی فایلها در سرور دیگری است و فشار زیادی به سرور اصلی وارد نمیشود و همچنین دسترسی به فایلها هم امن و محدود است.
امیدوارم مفید بوده باشه.