logout_user 只是把会话里的用户 id 删掉,数据库里一个字都不改。 登出挂 POST:GET 能被一个图片标签触发,把用户莫名其妙踢下线。 模板里一个 if current_user.is_authenticated 就能管两套导航,不需要别的机关。
这一篇对应教材 3.5 到 3.5.2:建立登出功能、确认运作、显示登入状态。 这是第 3 章最后一篇,收完这个尾,验证系统就完整了。
一、登出为什么挂 POST
实测 GET /auth/logout 返回 405,路由在、方法不对。 405 和 404 要分清:405 是「地址对了但你没用对方法」,404 是「根本没有这个地址」。 排错时这两个数字指向完全不同的方向。 挂 POST 的理由和删除一样:GET 可以被一个 img 标签、一次预加载、一个爬虫触发, 用户什么都没点就被踢下线。
「改状态的操作一律 POST」这条规矩,登出也算。
二、logout_user 只删一个键
实测登入后 GET /users 是 200,POST 登出返回 302 到 /, 再访问 /users 就变成 302 到 /auth/login?next=%2Fusers。 它做的事只有一件:把会话里的 _user_id 删掉。数据库里那条用户记录一动不动。 所以登出之后随时能再登入,账号没有任何变化。
反过来说:会话是客户端的东西,服务器没法「强行」让人下线—— 除非改 SECRET_KEY(所有人一起失效)或者在用户表上加一个版本号。 这也是为什么「改密码后踢掉其他设备」需要额外设计。
三、页面上的登录状态
{% if current_user.is_authenticated %}
<span>你好,{{ current_user.name }}</span>
<form method="post" action="{{ url_for('auth.logout') }}">
<button>登出</button></form>
{% else %}
<a href="{{ url_for('auth.login') }}">登入</a>
{% endif %}
实测未登入时导航区渲染出的是一个登入链接,登入后变成「你好,张三」加一个登出按钮。 模板里只写了这一个判断,两个状态各渲染一套。这就是登录状态显示的完整做法。 注意登出那个表单:method 是 post,action 指向 auth.logout。写成链接的话,点一下就 405。
current_user 在模板里可以直接用,不用传。flask-login 注册了上下文处理器。
四、三章下来的家底
| 能力 | 靠什么 | 在哪一篇 |
|---|---|---|
| 分模块 | Blueprint | 2.1 与 3.1 |
| 存数据 | SQLAlchemy 与迁移 | 2.3 至 2.5 |
| 管用户 | flask-login | 3.2 |
| 注册登入登出 | 表单加会话 | 3.3 至 3.5 |
第 1 章讲语法,第 2 章搭骨架,第 3 章加身份。三章合起来是一个能上线的后台雏形。 它还不完整的地方很明显:没有权限分级、没有找回密码、没有邮箱验证。 但这三样都是在同一套骨架上加,不需要推翻任何东西。
五、动手 Demo:登出功能与登录状态显示
这个 Demo 先验证 405,再看登入登出时会话怎么变,最后把导航条换成带状态的一版。 第四节那张表把前三章攒下来的能力列了个总账。
@bp.route("/logout", methods=["POST"])
@login_required
def logout():
logout_user()
return redirect(url_for("main.index"))
实跑输出:
--------------------------------------------------------------------
登出功能与登录状态显示
--------------------------------------------------------------------
① 登出为什么挂 POST
--------------------------------------------------------------------
GET /auth/logout -> 405
! 405 说明路由在、方法不对。
登出挂 POST 的理由和删除一样:一个 img 标签就能把用户踢下线,虽然不丢数据,但很烦。
② 登入再登出,看会话怎么变
--------------------------------------------------------------------
登入后 GET /users -> 200
POST /auth/logout -> 302,Location=/
登出后 GET /users -> 302,Location=/auth/login?next=%2Fusers
! logout_user() 只是把会话里的 _user_id 删掉,它不会去数据库里改任何东西。
所以登出后那条用户记录还在,随时能再登入。
③ 页面上的登录状态
--------------------------------------------------------------------
未登入时导航区 -> <a href="/auth/login">登入</a>
登入后导航区 -> <span>你好,张三</span> | <button>登出</button></form>
! 模板里只写了 if current_user.is_authenticated 这一个判断,两个状态各渲染一套。
这就是登录状态显示的完整做法,没有别的机关。
④ 三章下来手里有什么
--------------------------------------------------------------------
| 能力 | 靠什么 | 在哪一篇 |
|---|---|---|
| 分模块 | Blueprint | 2.1 与 3.1 |
| 存数据 | SQLAlchemy 与迁移 | 2.3 至 2.5 |
| 管用户 | flask-login | 3.2 |
| 注册登入登出 | 表单 + 会话 | 3.3 至 3.5 |
第 3 章到此结束。下一篇开始做正事:把物件侦测模型接进来。
第一节的 405 是「路由在、方法不对」,不是「地址写错了」。 第二节登出后访问 /users 又变回 302,说明会话真的清干净了。 第三节两行输出是两套导航:一个只有登入链接,一个是「你好,张三」加登出按钮。
30 秒实验
- 把登出路由的 methods 改成 GET,用一个 img 标签指向它,看页面加载时会发生什么。
- 登出时不清会话,只 redirect,看用户是否还是登录状态。
- 想一想:为什么「改密码后让其他设备下线」不能靠 logout_user 解决?
常见误区
- 登出挂 GET:被图片标签或预加载误触发,用户莫名掉线。
- 以为登出会改数据库:它只删会话里的一个键,记录一直在。
- 模板里拿 current_user 判空:它不是 None,判空永远为假。
- 登出表单忘了 method="post":默认是 GET,点一下 405。
自测三问(点开对答案)
1. logout_user 做了什么? —— 把会话里的用户 id 删掉,数据库里什么都不改。 2. 405 和 404 差在哪? —— 405 是路由在但方法不对,404 是压根没有这个地址。 3. 模板里怎么判断有没有登录? —— 用 current_user.is_authenticated,它在未登入时是 False 而不是报错。第 3 章到此结束。下一篇开始做正事:把物件侦测模型接进来。
- ☐ (本篇完,下一篇 4.1 物件侦测应用的六个页面)