Flask Session 与 Cookie
HTTP 协议本身是无状态的——服务器不会自动记住两次请求是否来自同一用户。
Flask 通过 Session 机制解决了这个问题,让 Web 应用能够「记住」用户的状态。
Session 的工作原理
Flask 默认使用 SecureCookieSession,它的工作方式是:
- Session 数据被序列化后用密钥签名,保存在浏览器的 Cookie 中
- 每次请求时,浏览器自动带上这个 Cookie
- Flask 验证签名后,将数据还原为 Python 字典供你使用
用户可以看到 Cookie 中的内容(因为是 base64 编码的),但无法篡改——因为任何修改都会导致签名验证失败。
配置 SECRET_KEY
要使用 Session,必须先设置 SECRET_KEY——用于签名的密钥。
实例
import secrets
from flask import Flask
app = Flask(__name__)
# 生成一个安全的随机密钥(只在首次运行时生成即可)
# secrets.token_hex() 产生 64 字符的十六进制随机字符串
app.secret_key = secrets.token_hex()
# 或者直接设置一个固定值(方便开发,但不要用在生产环境)
# app.secret_key = "dev-secret-key-change-in-production"
from flask import Flask
app = Flask(__name__)
# 生成一个安全的随机密钥(只在首次运行时生成即可)
# secrets.token_hex() 产生 64 字符的十六进制随机字符串
app.secret_key = secrets.token_hex()
# 或者直接设置一个固定值(方便开发,但不要用在生产环境)
# app.secret_key = "dev-secret-key-change-in-production"
重要:SECRET_KEY 必须保密且足够随机。如果密钥泄露,任何人都可以伪造你应用的 Session 数据。生产环境应从环境变量或配置文件读取,不要硬编码在代码中。
生成高质量密钥的命令:
$ python -c 'import secrets; print(secrets.token_hex())' 192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf
读写 Session
session 是一个类似字典的对象,用法和 Python 字典基本一样:
实例
from flask import Flask, session, redirect, url_for, request
app = Flask(__name__)
app.secret_key = "dev-secret-key"
@app.route("/")
def index():
# 检查 session 中是否有 username
if "username" in session:
return f"""
<h1>欢迎回来,{session["username"]}!</h1>
<p>你的访问次数:{session.get("visits", 0)}</p>
<p><a href="/logout">退出登录</a></p>
"""
return """
<h1>RUNOOB 首页</h1>
<p>你还未登录。</p>
<p><a href="/login">去登录</a></p>
"""
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username", "")
if username:
# 将用户名存入 session
session["username"] = username
# 初始化访问计数
session["visits"] = 0
return redirect(url_for("index"))
return """
<form method="post">
<h2>登录 RUNOOB</h2>
<p><input type="text" name="username" placeholder="输入用户名"></p>
<p><input type="submit" value="登录"></p>
</form>
"""
@app.route("/logout")
def logout():
# 清除 session 中的所有数据
session.clear()
# 或者只删除特定 key:
# session.pop("username", None)
return redirect(url_for("index"))
app = Flask(__name__)
app.secret_key = "dev-secret-key"
@app.route("/")
def index():
# 检查 session 中是否有 username
if "username" in session:
return f"""
<h1>欢迎回来,{session["username"]}!</h1>
<p>你的访问次数:{session.get("visits", 0)}</p>
<p><a href="/logout">退出登录</a></p>
"""
return """
<h1>RUNOOB 首页</h1>
<p>你还未登录。</p>
<p><a href="/login">去登录</a></p>
"""
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username", "")
if username:
# 将用户名存入 session
session["username"] = username
# 初始化访问计数
session["visits"] = 0
return redirect(url_for("index"))
return """
<form method="post">
<h2>登录 RUNOOB</h2>
<p><input type="text" name="username" placeholder="输入用户名"></p>
<p><input type="submit" value="登录"></p>
</form>
"""
@app.route("/logout")
def logout():
# 清除 session 中的所有数据
session.clear()
# 或者只删除特定 key:
# session.pop("username", None)
return redirect(url_for("index"))
永久 Session
默认情况下,Session 在浏览器关闭后失效(浏览器会话级别)。
如果需要「记住我」功能,可以将 Session 标记为 永久(permanent):
实例
from datetime import timedelta
# 设置永久 session 的有效期(默认 31 天)
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
@app.post("/login-remember")
def login_remember():
username = request.form.get("username")
if username:
session["username"] = username
# 标记为永久 session,浏览器关闭后仍有效
session.permanent = True
return redirect(url_for("index"))
# 设置永久 session 的有效期(默认 31 天)
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
@app.post("/login-remember")
def login_remember():
username = request.form.get("username")
if username:
session["username"] = username
# 标记为永久 session,浏览器关闭后仍有效
session.permanent = True
return redirect(url_for("index"))
Message Flashing——一次性消息
用户提交表单后,需要显示「操作成功」或「出错」的反馈信息。
这种「显示一次后消失」的消息,Flask 提供了 flash() 机制。
实例
from flask import Flask, flash, get_flashed_messages, redirect, render_template, request, url_for
app = Flask(__name__)
app.secret_key = "dev-secret-key"
@app.route("/post", methods=["GET", "POST"])
def create_post():
if request.method == "POST":
title = request.form.get("title", "").strip()
if not title:
# 保存错误消息到 flash,分类为 "error"
flash("标题不能为空", "error")
return redirect(url_for("create_post"))
# 保存成功消息到 flash,分类为 "success"
flash(f"文章「{title}」发布成功!", "success")
return redirect(url_for("create_post"))
return render_template("create_post.html")
@app.route("/flash-demo")
def flash_demo():
# 获取所有已 flash 的消息(读取后自动清除)
messages = get_flashed_messages(with_categories=True)
return render_template("flash_demo.html", messages=messages)
app = Flask(__name__)
app.secret_key = "dev-secret-key"
@app.route("/post", methods=["GET", "POST"])
def create_post():
if request.method == "POST":
title = request.form.get("title", "").strip()
if not title:
# 保存错误消息到 flash,分类为 "error"
flash("标题不能为空", "error")
return redirect(url_for("create_post"))
# 保存成功消息到 flash,分类为 "success"
flash(f"文章「{title}」发布成功!", "success")
return redirect(url_for("create_post"))
return render_template("create_post.html")
@app.route("/flash-demo")
def flash_demo():
# 获取所有已 flash 的消息(读取后自动清除)
messages = get_flashed_messages(with_categories=True)
return render_template("flash_demo.html", messages=messages)
模板中渲染 flash 消息:
实例
<!-- 文件路径:templates/create_post.html -->
<!DOCTYPE html>
<html>
<head>
<title>发布文章 - RUNOOB</title>
<style>
.flash-success { color: green; background: #e8f5e9; padding: 10px; border-radius: 4px; }
.flash-error { color: red; background: #ffebee; padding: 10px; border-radius: 4px; }
.flash-info { color: blue; background: #e3f2fd; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>发布文章</h1>
<!-- 获取并显示所有 flash 消息 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<p>标题:<input type="text" name="title" style="width:300px;"></p>
<p><input type="submit" value="发布"></p>
</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>发布文章 - RUNOOB</title>
<style>
.flash-success { color: green; background: #e8f5e9; padding: 10px; border-radius: 4px; }
.flash-error { color: red; background: #ffebee; padding: 10px; border-radius: 4px; }
.flash-info { color: blue; background: #e3f2fd; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>发布文章</h1>
<!-- 获取并显示所有 flash 消息 -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="post">
<p>标题:<input type="text" name="title" style="width:300px;"></p>
<p><input type="submit" value="发布"></p>
</form>
</body>
</html>
| 分类 | 推荐使用场景 |
|---|---|
| "message"(默认) | 一般通知 |
| "success" | 操作成功 |
| "error" | 操作失败 |
| "warning" | 警告信息 |
| "info" | 普通信息 |
Flash 消息依赖于 Session,因此必须设置 SECRET_KEY 才能使用。
Session 常见问题
Cookie 大小限制:浏览器对单个 Cookie 的大小限制约为 4KB。不要在 Session 中存储大量数据——适合存用户 ID、偏好设置等轻量信息,不适合存文件或大型对象。
