尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django用户认证与中间件登录拦截:学生评价系统核心链路解析

Django用户认证与中间件登录拦截:学生评价系统核心链路解析 简介一份已获通过的Python毕业设计项目基于Django框架实现学生综合能力教学评价系统适合计算机相关专业学生借鉴也适合想掌握用户认证与中间件开发的Python学习者。项目围绕登录、注销、注册等核心模块展开借助Django内置用户模型完成数据存储与校验并通过SignMiddleware中间件统一检查登录状态未登录时自动跳转登录页面可作为理解Web应用权限控制链条的完整范例。压缩包内共114个文件涵盖Python源码、HTML模板、JavaScript/CSS样式、前端图标字体、SQLite数据库及Excel样例等整体仅1.76MB结构清晰便于按模块查阅和二次开发。目前已有673人学习浏览项目提供了从用户认证到功能模块组织的完整实现思路尤其适合毕业设计选题、课程设计改造或Django实战练习时直接参考。1. 基于Python的学生综合能力教学评价系统先别写评价算法把认证链路理清带过毕业设计的人都有一个共识学生综合能力教学评价系统这类题目答辩老师真正会动手点开看的不是评价维度设计得多漂亮而是登录能不能进、换个浏览器会不会被拦截、评价记录能不能分页查出来。这套基于Python Django构建的毕业设计项目把用户认证收敛在sign_res模块中直接复用Django官方用户模型做登录、注销、注册的数据验证再用一个SignMiddleware做全校路由的登录状态拦截前端用AdminLTE套件搭建管理界面。整套代码里最值得反复拆的是认证与权限控制链路而不是评分公式本身这也是今天要展开讲的部分。2. sign_res 模块代码级拆解Django 用户登录、注销与注册的数据流教学评价系统的用户角色主要是教师和学生。Django内置的User模型已经覆盖了用户名、密码哈希、邮箱、is_staff等基础字段对大部分毕业设计题材够用如果还要记录学号、班级、教师工号可以在AbstractUser上扩展。原项目里的sign_res模块没有过度设计直接复用django.contrib.auth中的User对象完成create_user、authenticate、login、logout四件事对应的视图和模板通过login_Res文件夹统一管理整体结构清晰。2.1 登录视图中的 authenticate 与 login 两层分工# sign_res/views.py from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def user_login(request): 登录视图GET 渲染表单POST 做身份校验 if request.user.is_authenticated: return redirect(/index/) if request.method POST: username request.POST.get(username, ).strip() password request.POST.get(password, ) # authenticate 会遍历 AuthBackend # 默认后端会做 PBKDF2 密码哈希比对 user authenticate(request, usernameusername, passwordpassword) if user is not None: login(request, user) # 写入 session 并刷新 last_login return redirect(/index/) # 统一错误提示避免泄露“用户不存在”或“密码错误” return render(request, login_Res/login.html, { error: 账号或密码不正确, }) return render(request, login_Res/login.html)这里核心是authenticate和login是两层独立的操作。authenticate只负责核对用户名和密码哈希返回的是User实例或Nonelogin不再判断密码它把已经过认证的用户ID写进服务端 session。如果把两者混在一层处理经常会出现“authenticate 成功但是登录态持续不过一个请求”的怪问题。参数上username用strip()去掉首尾空格浏览器自动填充时常会带进不可见字符密码不做前后端可逆处理Django 的check_password会处理明文比对。需要注意在 POST 流程里request.user.is_authenticated这个前置判断登录成功的用户再提交登录表单会直接跳过避免重复写 session。注销视图也放在 sign_res 模块中from django.contrib.auth import logout def user_logout(request): 登出视图清除 session 后回到登录页 logout(request) return redirect(/login/)登出操作虽然简单但logout会同时 flush 掉 session 数据和 cookie 中的 sessionid避免在公共电脑上留下登录痕迹。路由配置上三个功能各自独立 URL# sign_res/urls.py from django.urls import path from . import views urlpatterns [ path(login/, views.user_login, namelogin), path(register/, views.user_register, nameregister), path(logout/, views.user_logout, namelogout), ]真实项目中 login_Res 模板文件夹里的页面通过 URL 反向解析互相跳转始终用{% url login %}而不是硬编码/login/这样部署到子目录时不用大改模板。2.2 注册视图与 create_user 的密码哈希时机注册视图是另一个容易翻车的位置。很多初学者用User.objects.create(username..., password...)直接创建用户结果密码以明文落库Django admin 后台里会显示Invalid password format。正确做法是调用create_user它内部先执行set_password再做保存落库的是pbkdf2_sha256格式的密文。def user_register(request): 注册视图校验用户名唯一性后创建账号 if request.method POST: username request.POST.get(username, ).strip() password request.POST.get(password, ) confirm request.POST.get(confirm_password, ) # 前后端两侧都要有基础校验后端防线才是有效的 if len(username) 3 or len(password) 6: return render(request, login_Res/register.html, { error: 用户名至少3位密码至少6位, }) if password ! confirm: return render(request, login_Res/register.html, { error: 两次密码输入不一致, }) # exists 查询走数据库唯一索引减少重复注册写库压力 if User.objects.filter(usernameusername).exists(): return render(request, login_Res/register.html, { error: 该用户名已被注册, }) # create_user 内部会调 set_password落库前完成 hashing user User.objects.create_user( usernameusername, passwordpassword, is_activeTrue, ) return redirect(/login/) return render(request, login_Res/register.html)上面的代码我额外加了用户名长度和密码长度校验这些约束在毕业设计里最好前端后端各做一次。需要注意exists()后接create_user并不是原子操作极端并发下两个请求可能同时通过 exists 检查再由唯一索引兜底抛IntegrityError。毕设范围内用 exists 足够但如果系统要放进生产建议用get_or_create或在异常里捕获IntegrityError。2.3 login_Res 模板组织方式与 CSRF 防护项目把登录注册模板放在 login_Res 文件夹静态资源则归到 AdminLTE 的 dist 和 plugins 目录包括adminlte.min.css、bootstrap.min.css、all.min.css、all.css等。登录模板表单区用模板变量渲染错误消息Django 的{% csrf_token %}是 POST 表单里不能少的一项。字段模板变量说明校验时机用户名username登录凭证唯一索引后端 length exists密码password不落明文哈希存库后端 len 6重复密码confirm_password仅注册页使用后端二次比对错误信息error模板里的表单提示由视图渲染CSRF tokencsrf_token防止跨站请求伪造模板内置CSRF 防护是 Django 默认开启的机制任何 POST 请求都必须带{% csrf_token %}。前端如果用 fetch 提交需要从 cookie 读取csrftoken并放进请求头X-CSRFToken漏掉时后端返回CSRF token missing or incorrect这是登录模块出现 403 的最常见原因。3. SignMiddleware 登录拦截机制不能只靠视图装饰器教学评价系统里有教师录入、学生查看、管理员维护三类页面。如果每个视图都手写if not request.user.is_authenticated再跳转代码会高度重复还容易漏掉个别路由。原项目用SignMiddleware统一处理登录状态检查注册进settings.MIDDLEWARE后所有到达视图的请求都会先过这一关。3.1 中间件在 Django 请求生命周期中的位置Django 的中间件按settings.MIDDLEWARE列表顺序执行每个中间件的__call__方法拿到request后可以决定继续向下调用get_response也可以直接返回响应。SignMiddleware 必须放在 session 和 auth 两个内置中间件之后否则request.user还没有被挂载所有请求读到的是AnonymousUser判断结果全部失真。# sign_res/middlewares.py import re from django.shortcuts import redirect class SignMiddleware: 登录检查中间件白名单之外的路由必须携带有效登录态 def __init__(self, get_response): # 服务启动时只初始化一次持有 get_response 引用 self.get_response get_response def __call__(self, request): # 放行的地址登录、注册、静态资源和验证码 white_list [ r^/login/$, r^/register/$, r^/static/.*, r^/captcha/.*, ] current_path request.path # 用短路逻辑判断当前路径是否属于免检范围 need_check not any( re.match(pattern, current_path) for pattern in white_list ) if need_check and not request.user.is_authenticated: # next 参数让登录成功后可跳回原页面 return redirect(/login/?next current_path) response self.get_response(request) return response/static/.*这一行非常关键。Django 开发服务器通过静态文件视图输出adminlte.min.css、bootstrap.min.css这些资源如果白名单没放行用户看到的登录页没有样式刷新后不断重定向回登录页排查起来很容易走弯路。写正则时用re.match代替re.search是为了只匹配路径前缀避免^/static/被拼在路径中间绕过检查。3.2 next 回跳参数与开放重定向防护跳转时拼接的next参数是中间件方案相比装饰器的显著优势。登录页表单的 action 里留一个隐藏字段保存该值登录成功后回到用户原本想访问的页面而不是每次都被丢进首页。# sign_res/views.py 登录接口中的回跳逻辑 from django.http import HttpResponseRedirect def user_login(request): # ...账号校验逻辑省略... if user is not None: login(request, user) next_url request.GET.get(next, /index/) # 防止 open redirect限制只能跳站内路径 if not next_url.startswith(/) or next_url.startswith(//): next_url /index/ return HttpResponseRedirect(next_url)next参数如果被外部拼成//evil.com浏览器会将它解析为协议相对的外部地址形成开放重定向漏洞。这个防护在系统里不会主动暴露但答辩时能解释清楚属于明显的加分点。3.3 注册进 settings 后的排错顺序把 SignMiddleware 挂进配置文件只需要一行注册# settings.py MIDDLEWARE [ django.contrib.sessions.middleware.SessionMiddleware, django.middleware.csrf.CsrfViewMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, sign_res.middlewares.SignMiddleware, ]常见问题集中在顺序和静态资源放行两处而不是中间件内部逻辑。出现“登录成功后马上回到登录页”时先检查中间件是否排在 AuthenticationMiddleware 之前出现“页面没有样式”时看 Network 面板里 CSS 请求的状态码是不是 302。现象可能原因排查方式登录后立即跳回 /login/SignMiddleware 顺序靠前查看 settings.MIDDLEWARE页面完全没有样式/static/ 未放行浏览器 Network 看 CSS 状态码POST 接口统一 403CSRF 中间件拦截检查模板 csrf_token登录态隔段时间丢失session 过期配置查看 SESSION_COOKIE_AGE提示python manage.py check不会检查中间件顺序建议在 SignMiddleware 里临时打印current_path和request.user.is_authenticated配合日志定位更高效。4. 学生综合能力评价的数据模型与 DataTables 分页设计评价系统的核心业务不在登录而在评价数据本身。需要解决的关键问题有三个维度怎么定义、评分记录怎么存、教师端列表怎么在不卡页面的情况下展示大量记录。前端用 DataTables 消费后端 JSON 接口这种拆分方式与 AdminLTE 自带的 Bootstrap 4 表格风格直接兼容。4.1 评价维度与评分记录的模型设计将评价维度固定为学业成绩、实践创新、综合素养三项用 Djangochoices约束合法值查询时通过get_dimension_display()拿中文描述。这样比建一张维度表更轻量也足够覆盖毕设场景。# evaluation/models.py from django.db import models from django.contrib.auth import get_user_model User get_user_model() class EvaluationRecord(models.Model): 学生单维度评价记录 DIMENSION_CHOICES [ (academic, 学业成绩), (practice, 实践创新), (quality, 综合素养), ] student models.ForeignKey( User, on_deletemodels.CASCADE, related_nameevaluated_records, verbose_name被评学生 ) teacher models.ForeignKey( User, on_deletemodels.SET_NULL, nullTrue, related_namegiven_records, verbose_name评分教师 ) dimension models.CharField( max_length16, choicesDIMENSION_CHOICES, verbose_name评价维度 ) score models.DecimalField( max_digits5, decimal_places2, verbose_name得分, help_text0~100 分 ) comment models.TextField(blankTrue, verbose_name评语) created_at models.DateTimeField(auto_now_addTrue, verbose_name评价时间) class Meta: db_table eval_record ordering [-created_at] def __str__(self): return f{self.student_id}-{self.dimension}-{self.score}外键的on_delete参数值得细说。教师被删时评分记录需要保留所以用SET_NULL学生是评价主体删除后评价记录没有意义用CASCADE顺带清理。related_name则避免后续查询出现evaluated_record_set这样的默认名。score 字段选DecimalField而不是FloatField因为浮点数在加权汇总时会积累精度误差评分系统对数值精度敏感。4.2 对接 DataTables 的 JSON 接口语义DataTables 开启服务端模式后每次翻页或搜索都会向接口发送一组固定参数后端必须按约定返回对应结构。前端参数示例值后端读取方式作用draw1request.GET.get(draw)请求序号原样返回start0request.GET.get(start)查询起始偏移length10request.GET.get(length)单页返回条数search[value]李雷request.GET.get(search[value])关键字搜索order[0][column]3request.GET.get(order[0][column])排序列索引后端返回结构里必须包含recordsTotal、recordsFiltered、data三块draw原样回传前端才能把异步响应对应到正确的表格实例。下面代码实现分页和维度筛选# evaluation/views.py from django.http import JsonResponse def evaluation_records_api(request): DataTables 服务端接口分页返回评价记录 draw int(request.GET.get(draw, 1)) start int(request.GET.get(start, 0)) length int(request.GET.get(length, 10)) dimension request.GET.get(dimension, ) # select_related 减少学生信息查询次数 queryset EvaluationRecord.objects.select_related(student).all() if dimension: queryset queryset.filter(dimensiondimension) total queryset.count() # 按 start/length 直接切片等价于 SQL limit offset page_queryset queryset[start:start length] data [] for record in page_queryset: data.append({ id: record.id, student: record.student.username, dimension: record.get_dimension_display(), score: str(record.score), comment: record.comment, time: record.created_at.strftime(%Y-%m-%d %H:%M), }) return JsonResponse({ draw: draw, recordsTotal: total, recordsFiltered: total, data: data, })这里没有用 Django 的 Paginator而是直接切片 queryset。原因在于start和length本身就是数据库的 limit offset直接切片少一次计数包装性能更直接。前提是查询集必须有序模型 Meta 里已加上ordering [-created_at]切片结果才是稳定的。4.3 AdminLTE 管理端资源加载顺序项目给出的资源包主体是 AdminLTE 体系包括主题样式、Bootstrap 基础样式、Select2 下拉组件、toastr 提示、DataTables 响应式表格样式。加载顺序直接决定页面最终效果link relstylesheet href{% static plugins/bootstrap/css/bootstrap.min.css %} link relstylesheet href{% static plugins/overlay-scrollbars/css/OverlayScrollbars.min.css %} link relstylesheet href{% static plugins/fontawesome-free/css/all.min.css %} link relstylesheet href{% static plugins/select2/css/select2.min.css %} link relstylesheet href{% static plugins/datatables-bs4/css/dataTables.bootstrap4.min.css %} link relstylesheet href{% static dist/css/adminlte.min.css %}adminlte.min.css依赖 Bootstrap 4 的栅格变量放到 Bootstrap 前会导致按钮和卡片错位DataTables 的主题样式又要求 jQuery 先加载。CSS 文件之间的覆盖关系比 JS 更隐蔽浏览器控制台里样式计算混乱时第一优先检查引入顺序而不是怀疑选择器写错。5. 答辩演示前的三组验证从中间件拦截到静态资源收集系统能不能顺利通过验收一半在功能一半在演示时的稳定性。下面这套验证流程上午在本地跑完下午就能直接上场演示。5.1 用 curl 模拟未登录请求验证 SignMiddleware先启动开发服务器对需要登录才能访问的页面发起请求# 未登录请求评价列表预期返回 302 并落到 /login/ curl -s -o /dev/null -w %{http_code} - %{redirect_url} \ http://127.0.0.1:8000/evaluation/list/输出中是302 - /login/?next/evaluation/list/说明中间件正常拦截并保留了 next 参数。再验证登录态# 保存 cookie 后带着 cookie 访问受保护页面 curl -s -c cookies.txt -b cookies.txt \ -d usernameteacher001passwordtest123456 \ http://127.0.0.1:8000/login/ curl -s -b cookies.txt \ http://127.0.0.1:8000/evaluation/list/ | grep 评价记录-c把服务端下发的 sessionid 写入本地文件-b在后续请求中带上 cookie。第二次请求返回 200 且页面包含“评价记录”说明登录态和中间件放行路径都正常。注意登录接口本身返回 302 时 curl 不会自动跟随需要加-L才能看到登录后的页面。5.2 初始化演示账号与评价数据答辩前准备教师、学生两种角色账号避免现场注册。可以用一个管理命令批量生成python manage.py seed_data --teachers 2 --students 20# evaluation/management/commands/seed_data.py from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model User get_user_model() class Command(BaseCommand): help 为答辩准备教师、学生账号和评价记录 def add_arguments(self, parser): parser.add_argument(--teachers, typeint, default2) parser.add_argument(--students, typeint, default20) def handle(self, *args, **options): for i in range(options[teachers]): User.objects.get_or_create( usernamefteacher{i1:02d}, defaults{is_staff: True}, ) for user in User.objects.filter(is_staffTrue): user.set_password(123456) user.save() self.stdout.write(self.style.SUCCESS(账号初始化完成密码: 123456))命令里的get_or_create保证重复执行不会产生重复账号固定密码便于现场快速登录。建议再生成每个学生三条维度的评价记录让 DataTables 表格在演示时出现翻页效果。5.3 迁移与静态文件收集的固定顺序换机演示或部署到服务器时最大的隐患是静态文件路径。执行顺序固定为makemigrations、migrate、collectstatic、runserver。collectstatic --noinput会把 dist 和 plugins 下的adminlte.min.css、bootstrap.min.css等全部复制到 STATIC_ROOT漏掉这一步本地样式正常部署后全部丢失。首次migrate时确认auth_user表已创建否则登录会报no such table: auth_user错误。本文还有配套的精品资源点击获取
返回列表