如何基于Django Rest Framework实现带用户认证的文件下载?
嘿,针对你这种跨域环境下带Token认证的文件下载需求,我给你整理了两个实用的方案,结合你的Django DRF + Nuxt.js栈来实现:
方案1:生成临时签名下载链接(推荐大文件)
这个思路是前端先请求后端获取一个临时的、已授权的下载链接,然后通过这个链接直接下载——不用在请求里带Token,既避免Token暴露,也更适合大文件(不用前端处理Blob占用内存)。
后端DRF代码示例
from django.core.signing import Signer, BadSignature, TimestampSigner from django.http import HttpResponse, HttpResponseNotFound, HttpResponseForbidden from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .models import YourFileModel # 替换成你的文件模型 # 生成临时下载链接的视图 class GenerateDownloadLinkView(APIView): permission_classes = [IsAuthenticated] def get(self, request, file_id): try: file_obj = YourFileModel.objects.get(id=file_id) # 用TimestampSigner给链接加15分钟有效期,防止链接长期有效 signer = TimestampSigner() # 加密用户ID+文件ID,确保只有该用户能下载 signed_value = signer.sign(f"{request.user.id}:{file_id}") download_url = f"http://localhost:8000/api/download/{signed_value}/" return Response({"download_url": download_url}) except YourFileModel.DoesNotExist: return Response({"error": "文件不存在"}, status=404) # 处理下载的视图 class FileDownloadView(APIView): def get(self, request, signed_value): signer = TimestampSigner() try: # 验证签名和有效期(默认15分钟) original_value = signer.unsign(signed_value, max_age=900) user_id, file_id = original_value.split(":") file_obj = YourFileModel.objects.get(id=file_id) # 额外验证用户权限(比如文件是否属于该用户) if str(file_obj.owner.id) != user_id: return HttpResponseForbidden("无下载权限") # 返回文件下载响应 response = HttpResponse(file_obj.file.open(), content_type='application/octet-stream') response['Content-Disposition'] = f'attachment; filename="{file_obj.name}"' return response except (BadSignature, YourFileModel.DoesNotExist): return HttpResponseNotFound("无效或过期的下载链接")
Nuxt.js前端代码示例
<template> <button @click="handleDownload(file.id)" class="download-btn"> 下载文件:{{ file.name }} </button> </template> <script setup> import { useAuthStore } from '~/stores/auth' // 假设Token存在Pinia/Vuex的auth仓库里 const authStore = useAuthStore() const file = { id: 1, name: "example.pdf" } // 替换成你的文件数据 const handleDownload = async (fileId) => { try { // 先请求后端生成临时下载链接 const response = await fetch(`http://localhost:8000/api/generate-download-link/${fileId}/`, { headers: { 'Authorization': `Token ${authStore.token}` } }) if (!response.ok) throw new Error('生成链接失败') const data = await response.json() // 打开新窗口下载 window.open(data.download_url, '_blank') } catch (error) { alert(`下载出错:${error.message}`) console.error(error) } } </script>
方案2:前端直接请求文件并处理Blob(适合小文件)
如果是小文件,可以直接通过fetch请求后端的认证下载接口,带上Token,拿到Blob后创建本地下载链接——流程更短,不用额外生成临时链接。
后端DRF代码示例
from django.http import HttpResponse, HttpResponseForbidden from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from .models import YourFileModel class AuthenticatedFileDownloadView(APIView): permission_classes = [IsAuthenticated] def get(self, request, file_id): try: file_obj = YourFileModel.objects.get(id=file_id) # 验证用户权限 if file_obj.owner != request.user: return HttpResponseForbidden("无下载权限") response = HttpResponse(file_obj.file.open(), content_type='application/octet-stream') response['Content-Disposition'] = f'attachment; filename="{file_obj.name}"' return response except YourFileModel.DoesNotExist: return HttpResponse("文件不存在", status=404)
Nuxt.js前端代码示例
<template> <button @click="handleBlobDownload(file.id)" class="download-btn"> 下载小文件:{{ file.name }} </button> </template> <script setup> import { useAuthStore } from '~/stores/auth' const authStore = useAuthStore() const file = { id: 2, name: "small-file.txt" } const handleBlobDownload = async (fileId) => { try { const response = await fetch(`http://localhost:8000/api/download-file/${fileId}/`, { headers: { 'Authorization': `Token ${authStore.token}` } }) if (!response.ok) throw new Error('下载请求失败') const blob = await response.blob() // 从响应头获取文件名(可选,后端需要返回正确的Content-Disposition) const contentDisposition = response.headers.get('Content-Disposition') const filename = contentDisposition?.split('filename="')[1]?.split('"')[0] || '未知文件' // 创建临时下载链接 const url = window.URL.createObjectURL(blob) const aTag = document.createElement('a') aTag.href = url aTag.download = filename aTag.target = '_blank' document.body.appendChild(aTag) aTag.click() // 清理资源 window.URL.revokeObjectURL(url) document.body.removeChild(aTag) } catch (error) { alert(`下载出错:${error.message}`) console.error(error) } } </script>
必配的CORS配置(Django端)
因为是跨域部署,一定要在settings.py里配置允许Nuxt的域名和Token请求头:
CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", ] CORS_ALLOW_HEADERS = [ "authorization", "content-type", ]
方案选择建议
- 如果是大文件,优先选方案1:不用前端处理Blob,不会占用浏览器内存,而且临时链接有有效期,更安全。
- 如果是小文件,方案2更简洁:少一次请求,流程更短。
内容的提问来源于stack exchange,提问作者Aamu
相关产品推荐
相关产品推荐

