news 2026/8/26 18:56:01

零基础入门python23:Flask-Login登录、退出与会话

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
零基础入门python23:Flask-Login登录、退出与会话

零基础入门python23:Flask-Login登录、退出与会话

一、上一篇课后练习讲解

邮箱大小写测试应先用Alice@Example.com注册,再用alice@example.com登录,并断言数据库只有一条用户记录。归一化应发生在查询和写入之前。

上一篇课后练习完整答案

上一篇练习的要求已落实到下面完整文件;先运行项目测试,再用 curl 对照状态码和数据库持久化结果
答案要点:注册先规范化邮箱和校验密码,再生成带盐哈希;唯一约束冲突捕获 IntegrityError 后 rollback 并返回 409。

文件:app/auth.py

完整参考答案文件

完整文件:app/auth.py

fromflaskimportBlueprint,requestfromsqlalchemy.excimportIntegrityErrorfromwerkzeug.securityimportgenerate_password_hashfrom.extensionsimportdbfrom.modelsimportUser bp=Blueprint("auth",__name__,url_prefix="/api/auth")@bp.post("/register")defregister():data=request.get_json(silent=True)or{}email=str(data.get("email","")).strip().casefold()password=str(data.get("password",""))if"@"notinemailorlen(password)<8:return{"error":"input_invalid"},400db.session.add(User(email=email,password_hash=generate_password_hash(password)))try:db.session.commit()exceptIntegrityError:db.session.rollback()return{"error":"email_exists"},409return{"email":email},201

完整参考答案文件

本篇对应的交付源码完整文件:flask-ledger/app/auth.py

fromflaskimportBlueprint,requestfromflask_loginimportcurrent_user,login_required,login_user,logout_userfrom.extensionsimportdbfrom.modelsimportUser bp=Blueprint("auth",__name__,url_prefix="/api/auth")@bp.post("/register")defregister():data=request.get_json(silent=True)or{}email=str(data.get("email","")).strip().lower()password=str(data.get("password",""))if"@"notinemail:return{"message":"邮箱格式不正确"},400iflen(password)<8:return{"message":"密码至少8位"},400ifdb.session.scalar(db.select(User).where(User.email==email)):return{"message":"邮箱已注册"},409user=User(email=email)user.set_password(password)db.session.add(user)db.session.commit()return{"id":user.id,"email":user.email},201@bp.post("/login")deflogin():data=request.get_json(silent=True)or{}email=str(data.get("email","")).strip().lower()user=db.session.scalar(db.select(User).where(User.email==email))ifnotuserornotuser.check_password(str(data.get("password",""))):return{"message":"邮箱或密码错误"},401login_user(user)return{"id":user.id,"email":user.email}@bp.post("/logout")@login_requireddeflogout():logout_user()return{"message":"已退出"}@bp.get("/me")@login_requireddefme():return{"id":current_user.id,"email":current_user.email}

验收命令:python -m pytest -q(Django 项目使用 python manage.py test)。预期测试通过;若失败先检查迁移、配置和事务回滚。

二、本篇完成什么

让注册用户登录并保持会话,完成/me查看当前用户和/logout退出。登录状态由 Flask-Login 从 session 恢复,受保护接口使用@login_required

三、为什么不用客户端传 user_id

/mecurrent_user.id读取身份,而不是接受 JSON 中的user_id。客户端提供的 id 只是普通输入,不能作为授权依据;身份必须来自已经验证的 session。

四、验收流程

curl-c cookies.txt-X POST http://127.0.0.1:5000/api/auth/login-H"Content-Type: application/json"-d"{\"email\":\"alice@example.com\",\"password\":\"secret123\"}"curl-b cookies.txt http://127.0.0.1:5000/api/auth/me curl-b cookies.txt-X POST http://127.0.0.1:5000/api/auth/logout curl-b cookies.txt http://127.0.0.1:5000/api/auth/me

预期登录和/me为 200,退出后/me为 401。测试必须覆盖错误密码、未登录访问和退出后会话失效。课后练习:增加会话过期配置,并解释开发环境和生产环境的 Cookie 安全属性差异。

项目增量:Flask-Login 会话边界

登录成功后使用安全 cookie 保存会话标识,服务器通过 user_loader 从数据库加载用户;注销必须清除会话,禁用用户不能继续访问。

@login_manager.user_loaderdefload_user(user_id):returndb.session.get(User,int(user_id))@auth_bp.post('/login')deflogin():user=User.query.filter_by(email=request.form['email'].strip().casefold()).first()ifuserisNoneornotuser.check_password(request.form['password']):abort(401)login_user(user)returnredirect(url_for('ledger.index'))

验收未登录访问账目返回 401 或重定向,登录后只能看到自己的数据;课后练习测试 session cookie 和禁用用户。

五、会话到底保存了什么

Flask-Login 默认在 cookie 中保存一个不可直接信任的用户标识,服务端每次请求通过user_loader查询用户。cookie 被篡改时签名校验会失败,current_user变成匿名用户;因此不能把“是否登录”交给前端传来的user_id

@login_manager.user_loaderdefload_user(user_id:str):# 统一从数据库加载,找不到用户时返回 None,框架会按匿名用户处理。try:returndb.session.get(User,int(user_id))except(TypeError,ValueError):returnNone@login_manager.unauthorized_handlerdefunauthorized():# API 客户端需要 JSON 和 401,而不是浏览器登录页的 302 跳转。return{"error":"login_required"},401

登录接口不应区分“邮箱不存在”和“密码错误”,否则攻击者可以枚举注册用户。remember会生成更长寿命的 cookie,只有在用户明确勾选时才启用。若项目改成前后端分离并使用 JWT,仍然需要保留同样的认证边界,只是令牌存储位置不同。

六、用测试客户端证明 cookie 生效

deftest_login_session(client,user):response=client.post("/api/auth/login",json={"email":user.email,"password":"correct-horse"})assertresponse.status_code==200me=client.get("/api/auth/me")assertme.status_code==200assertme.json["id"]==user.idclient.post("/api/auth/logout")assertclient.get("/api/auth/me").status_code==401

测试中必须复用同一个client,因为 cookie 存在客户端对象里。出现“明明登录了仍是 401”时,检查SECRET_KEY是否在每次创建应用时稳定,以及user_loader是否把字符串 id 转成整数;如果每次请求都创建新 client,会话当然不会保留。

七、禁用用户和会话失效

User增加is_active字段后,is_authenticated仍然可以为真,但is_active应在user_loader中检查。管理员禁用用户后,下一次请求应立即失效,而不是等 cookie 自然过期:

@login_manager.user_loaderdefload_user(user_id):user=db.session.get(User,int(user_id))returnuserifuseranduser.is_activeelseNone

八、上一篇练习讲解与本篇练习

上一篇的并发注册测试应使用两个独立 Session;本篇练习增加GET /api/auth/me和禁用用户测试,要求退出后再次访问返回 401。下一篇会在这个身份基础上实现分类 CRUD,并演示越权访问必须返回 404 还是 403。

九、Cookie 安全实验

在浏览器开发者工具的 Application → Cookies 中确认会话 cookie 具有HttpOnlySameSite=Lax;若线上使用 HTTPS,再打开Secure。把 cookie 的值改一个字符后重新访问/api/auth/me,应得到 401,说明签名校验生效。不要把 Flask 的签名 cookie 当作“加密数据库”,其中内容仍可能被读取;敏感资料应放服务端 Session 或数据库。

如果采用前后端分离,跨域请求还要正确设置 CORS 的supports_credentials,并限制允许的 origin;不能写*同时携带 cookie。完成实验后清理浏览器 cookie,避免旧会话干扰下一次测试。

本篇结束:完整模块文件

下面是交付项目中真实存在的完整文件 flask-ledger/app/auth.py。它覆盖本篇新增逻辑以及前文已经完成的依赖代码;复制单个函数会丢失上下文,因此这里提供整份文件。

fromflaskimportBlueprint,requestfromflask_loginimportcurrent_user,login_required,login_user,logout_userfrom.extensionsimportdbfrom.modelsimportUser bp=Blueprint("auth",__name__,url_prefix="/api/auth")@bp.post("/register")defregister():data=request.get_json(silent=True)or{}email=str(data.get("email","")).strip().lower()password=str(data.get("password",""))if"@"notinemail:return{"message":"邮箱格式不正确"},400iflen(password)<8:return{"message":"密码至少8位"},400ifdb.session.scalar(db.select(User).where(User.email==email)):return{"message":"邮箱已注册"},409user=User(email=email)user.set_password(password)db.session.add(user)db.session.commit()return{"id":user.id,"email":user.email},201@bp.post("/login")deflogin():data=request.get_json(silent=True)or{}email=str(data.get("email","")).strip().lower()user=db.session.scalar(db.select(User).where(User.email==email))ifnotuserornotuser.check_password(str(data.get("password",""))):return{"message":"邮箱或密码错误"},401login_user(user)return{"id":user.id,"email":user.email}@bp.post("/logout")@login_requireddeflogout():logout_user()return{"message":"已退出"}@bp.get("/me")@login_requireddefme():return{"id":current_user.id,"email":current_user.email}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 18:51:35

开源项目Tiger AI Platform平台中使用的模型详解:模型001-efficient-sam

EfficientSAM-Ti(OpenCV) 完全指南:原理、TigerPro 接入、代码实战与落地案例(efficient-sam) 系列:TigerPro AI 模型手册(1/110) 分类:交互分割 你能带走什么:原理边界、规格表、完整接入步骤、可运行代码、双案例、调参表、避坑与验收清单。 适用读者:CV_PyhonVue…

作者头像 李华
网站建设 2026/8/26 18:39:10

记录C++ 8

多态多态是指同一个接口可以有多种不同的实现方式,多态在c中分静态多态和动态多态&#xff1a;静态多态使用函数重载和泛型编程实现动态多态基于虚函数实现动态绑定与静态绑定定义&#xff1a;静态绑定指程序结束编译后就已经确定行为&#xff0c;动态绑定指在运行时确定行为作…

作者头像 李华
网站建设 2026/8/26 18:35:15

【官方原创】STM32CubeProgrammer 2.23新版本发布

我们很高兴地告知大家&#xff0c;STM32CubeProgrammer 2.23现已发布。 STM32CubeProgrammer 2.23是一次重大版本更新&#xff0c;本次迭代共处理290项工单&#xff0c;包含52项功能需求变更、238项缺陷修复&#xff0c;同时完成200个开发任务包。本次版本背后完成了大量底层优…

作者头像 李华
网站建设 2026/8/26 18:19:50

Altium Develop 学习资料整理

&#xff08;&#xff09;引言 Altium Develop 可以理解为 Altium Designer&#xff08;AD&#xff09; 与 Altium 365&#xff08;A365&#xff09; 能力的结合。它不仅包含传统的原理图设计、PCB 设计和规则检查功能&#xff0c;也进一步结合了工作区、项目协作、版本管理、…

作者头像 李华
网站建设 2026/8/26 18:19:41

内存为什么越跑越高?从对象回收讲透内存泄漏的原因与预防

线上服务有一种问题很让人头疼&#xff1a;刚启动时一切正常。运行几个小时后&#xff0c;内存开始慢慢上涨。再过一段时间&#xff0c;接口变慢、GC 变频繁&#xff0c;最后甚至直接 OOM。很多时候&#xff0c;这背后就是一个很典型的问题&#xff1a;内存泄漏。不少人会把内存…

作者头像 李华