You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:58:13