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

Django中如何阻止未购课用户访问对应课程的静态/媒体文件?

问题描述

我正在开发一个Udemy克隆平台,需要实现以下功能:

  • 未购课用户仅可查看课程详情
  • 购课用户可查看课程详情、课时及视频

现有方案存在漏洞:通过Course模型的外键字段存储有权限的用户,校验当前登录用户是否在该列表中,但未购课用户若知晓课程视频的直接链接,可通过地址栏直接访问该媒体/静态文件。

现有模型代码

class Course(models.Model):
    id = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4, editable=False)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='courses')
    students = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='courses', blank=True, null=True)
    ...

class Section(models.Model):
    id = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4, editable=False)
    course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='sections')
    ...

class Lesson(models.Model):
    id = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4, editable=False)
    section = models.ForeignKey(Section, on_delete=models.CASCADE, related_name="lessons")

    video = models.FileField(upload_to=directory_for_images, blank=True, max_length=500)
    ...

现有视图代码

def courseConditionalView(request, pk):
    course = models.Course.objects.get(pk = pk)
    current_user = request.user
    course_bought = False
    if current_user.is_authenticated:
        course_bought = bool(current_user.courses.filter(pk = pk))

    if course_bought:
        return redirect(reverse('course_details_study', kwargs={'pk': str(pk)}))
    else:
        return render(request, 'courses/course_detail.html', {'course': course})

def studyView(request, pk):
    course = models.Course.objects.get(pk = pk)
    current_user = request.user
    course_bought = False
    if current_user.is_authenticated:
        course_bought = bool(current_user.courses.filter(pk = pk))

    if course_bought:
        return render(request, 'courses/course_detail_study.html', {'course': course})
    else:
        return redirect(reverse(courseConditionalView, kwargs={'pk': str(pk)}))

请问如何阻止未购课用户访问这些静态/媒体文件?或者是否应采用更优的动态权限方案——在课程创建时动态生成权限,用户购课时为其分配对应权限?


解决方案

一、阻止未购课用户直接访问媒体文件

直接暴露媒体文件URL是核心漏洞,必须通过Django视图层中转访问,禁止Web服务器直接提供文件:

  1. 修正模型关联错误
    首先修复Course.students字段的类型——当前ForeignKey只能关联单个学生,改为ManyToManyField才符合多学生购课的场景:

    class Course(models.Model):
        # ... 其他字段
        students = models.ManyToManyField(get_user_model(), related_name='courses', blank=True)
    
  2. 编写视频中转视图
    创建专门的视图处理视频请求,先校验用户权限再返回文件:

    from django.http import FileResponse, Http404
    from django.shortcuts import get_object_or_404
    
    def serve_lesson_video(request, lesson_id):
        lesson = get_object_or_404(Lesson, id=lesson_id)
        course = lesson.section.course
        
        # 权限校验:已登录且购买了对应课程
        if not request.user.is_authenticated or not request.user.courses.filter(pk=course.id).exists():
            raise Http404("无权访问该视频")
        
        # 流式返回视频文件,支持大文件播放
        response = FileResponse(lesson.video.open('rb'), content_type='video/mp4')
        response['Content-Disposition'] = f'inline; filename="{lesson.video.name.split("/")[-1]}"'
        return response
    
  3. 配置URL路由
    将视频访问指向中转视图,而非直接指向媒体文件:

    path('lesson/<uuid:lesson_id>/video/', views.serve_lesson_video, name='serve_lesson_video'),
    
  4. 模板中生成正确链接
    在course_detail_study.html中,替换直接的媒体URL为视图路由:

    <video src="{% url 'serve_lesson_video' lesson.id %}" controls></video>
    
  5. 额外安全加固

    • 使用签名URL(如django-signed-urls)给视频链接添加过期时间,防止用户长期分享;
    • 添加访问频率限制,避免批量下载或爬虫攻击。

二、权限方案选择:动态权限vs基础关联方案

基础关联方案(推荐)

如果你的平台只需"购课即可访问全部内容"的逻辑,修改后的ManyToManyField关联方案足够用:

  • 实现简单,查询性能高;
  • 权限校验逻辑清晰,与现有视图代码兼容。

动态权限方案(复杂场景适用)

若需要细粒度权限控制(如部分用户可访问特定章节、讲师可编辑课程等),可采用动态权限:

  1. 课程创建时生成专属权限

    from django.contrib.auth.models import Permission
    from django.contrib.contenttypes.models import ContentType
    
    def create_course_permissions(course):
        content_type = ContentType.objects.get_for_model(Course)
        # 创建课程内容访问权限
        view_perm, _ = Permission.objects.get_or_create(
            codename=f'view_course_{course.id}',
            name=f'查看课程{course.id}的内容',
            content_type=content_type,
        )
        return view_perm
    
  2. 购课时分配权限

    def purchase_course(request, course_id):
        course = get_object_or_404(Course, id=course_id)
        perm = Permission.objects.get(codename=f'view_course_{course.id}')
        request.user.user_permissions.add(perm)
        course.students.add(request.user)  # 保持关联逻辑兼容
        return redirect('studyView', pk=course_id)
    
  3. 视图中校验权限

    if not request.user.has_perm(f'courses.view_course_{course.id}'):
        raise Http404
    

方案对比

  • 基础关联方案:开发成本低,适合绝大多数Udemy类平台需求;
  • 动态权限方案:扩展性强,但实现和维护成本更高,适合复杂权限场景。

内容的提问来源于stack exchange,提问作者iljuhenson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:28:26