flask-session 使用详解 案例详解
Flask中的会话是一种从一个请求到另一个请求存储有关特定用户的信息的方法。它们通过在用户浏览器上存储经过加密签名的cookie并在每次请求时对其进行解码来工作。
sesison对象可以像字典一样处理,该字典在请求中持久存在,使其成为存储非敏感用户数据的理想场所。
重要
会话对象不是存储数据的安全方法。它是base64编码的字符串,可以轻松解码,因此不能成为保存或访问敏感信息的安全方法。
我们将在稍后演示解码会话cookie。
在此示例中,我们将创建一个非常不安全的系统,以允许用户登录并查看其个人资料。
目的是演示会话对象,而不是安全的用户管理系统!该指南不包括任何密码哈希,用户反馈甚至是真实的数据库,仅用于演示如何使用会话。
登录页面
首先创建一个模板,允许我们的用户通过提供用户名和密码登录:
{% extends "public/templates/public_template.html" %}
{% block title %}sign up{% endblock %}
{% block main %}
<div class="container">
<div class="row">
<div class="col">
<h1>Sign in</h1>
<hr>
<form action="/sign-in" method="POST">
<div class="form-group">
<label>Username</label>
<input class="form-control" type="text" name="username">
</div>
<div class="form-group">
<label>Password</label>
<input class="form-control" type="password" name="password">
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
</div>
</div>
</div>
{% endblock %}
烧瓶进口
对于此示例,我们需要从Flask导入一些内容:
from flask import render_template, request, session, redirect, url_for
render_template
-允许我们向浏览器呈现模板request
-处理传入的表单数据和URLsession
-会话对象redirect
-允许我们将用户重定向到应用程序的各个部分url_for
-从参数构造URL
模拟数据库
我们还需要一个包含几个用户的模拟数据库(随意将值更改为更熟悉的值!):
users = {
"julian": {
"username": "julian",
"email": "julian@gmail.com",
"password": "example",
"bio": "Some guy from the internet"
},
"clarissa": {
"username": "clarissa",
"email": "clarissa@icloud.com",
"password": "sweetpotato22",
"bio": "Sweet potato is life"
}
}
我们只是使用包含2个字典的字典来表示我们的用户数据库。
密钥
会话对象要求您的应用为SECRET_KEY
变量设置一个值。您可以在应用程序配置文件中设置它,也可以在包含视图的文件中的某个位置提供它。
无论您做出什么决定,最好在分配app
变量后立即声明它。
密钥用于对会话cookie进行编码,因此建议使用相对复杂的东西。
生成私钥的一个好地方是使用secrets.token_urlsafe()
并将其传递给整数:
>>> import secrets
>>> secrets.token_urlsafe(16)
'OCML3BRawWEUeaxcuKHLpw'
继续创建SECRET_KEY
:
app.config["SECRET_KEY"] = "OCML3BRawWEUeaxcuKHLpw"
现在我们有了导入,数据库和密钥,让我们继续构建路由。
登录路线
我们需要一条路线来处理用户登录并设置会话对象:
@app.route("/sign-in", methods=["GET", "POST"])
def sign_in():
if request.method == "POST":
req = request.form
username = req.get("username")
password = req.get("password")
if not username in users:
print("Username not found")
return redirect(request.url)
else:
user = users[username]
if not password == user["password"]:
print("Incorrect password")
return redirect(request.url)
else:
session["USERNAME"] = user["username"]
print("session username set")
return redirect(url_for("profile"))
return render_template("public/sign_in.html")
如您所见,这是一条相对简单的路线,仅用于演示。
- 我们使用以下命令检查用户名是否在数据库中
if not username in users:
- 如果用户存在,我们将用户分配到
user
与user = users[username]
- 我们检查用户密码是否与数据库中该用户的密码匹配
if not password == user["password"]:
- 如果任何一项检查失败,我们将使用重定向回请求的URL
redirect(request.url)
如果两项检查均通过,则将用户名分配给会话密钥USERNAME
。
我们可以像对待Python字典一样对待会话对象。
更新会话
设置键和值:
session["KEY"] = "VALUE"
在这种情况下,我们已将会话USERNAME
密钥分配给用户用户名:
session["USERNAME"] = user["username"]
如果您print(session)
只是在设置USERNAME
密钥之后,您将看到(假设用户名为“ julian”):
<SecureCookieSession {'USERNAME': 'julian'}>
您还会注意到使用重定向到profile
,
return redirect(url_for("profile"))
重定向采用URL并将客户端重定向到该URL。在这种情况下,我们已经通过了url_for("profile")
。
url_for
接受参数并构建端点,在这种情况下,我们刚刚传递了函数的名称,该函数"profile"
将构建URL。
我们尚未创建个人资料路线,所以让我们继续吧:
获取会话对象
@app.route("/profile")
def profile():
if not session.get("USERNAME") is None:
username = session.get("USERNAME")
user = users[username]
return render_template("public/profile.html", user=user)
else:
print("No username found in session")
return redirect(url_for("sign_in"))
会话对象是全局的,这意味着我们可以从应用程序的任何部分访问它,并将其视为字典。
在profile
路线中,我们执行以下操作:
if not session.get("USERNAME") is None:
- 我们用于
session.get("KEY")
检查密钥是否存在于会话中。 - 如果键不存在,则
session.get("KEY")
返回None
user = users[session.get("USERNAME")]
username = session.get("USERNAME")
将username
变量分配给会话USERNAME
密钥中保存的值- 然后,我们从
users
字典中为用户分配user = users[username]
return render_template("public/profile.html", user=user)
- 如果在会话中找到该用户,我们将调用
render_template
该profile.html
文件并将其与user
else:
print("No username found in session")
return redirect(url_for("sign_in"))
- 如果
USERNAME
密钥不在会话中,我们将重定向回登录页面
现在我们需要创建个人资料页面!
个人资料页
创建一个名为的新文件,profile.html
并添加以下内容:
{% extends "public/templates/public_template.html" %}
{% block title %}Profile{% endblock %}
{% block main %}
<div class="container">
<div class="row">
<div class="col">
<h1>Profile</h1>
<hr>
<div class="card">
<div class="card-body">
<h4>{{ user["username"] }}</h4>
<hr>
<p>{{ user["email"] }}</p>
<p class="text-muted">{{ user["bio"] }}</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
这是一个非常简单的页面,其中包含一些从数据库中提取的用户详细信息。
在继续测试任何代码之前,让我们创建一条允许用户注销的路由。
弹出会话
我们需要创建一个USERNAME
从会话对象中清除的路由。
正如session
只是一个Python对象,我们可以从中弹出一个键!
让我们创建一个简单的登出路线:
@app.route("/sign-out")
def sign_out():
session.pop("USERNAME", None)
return redirect(url_for("sign_in"))
要从会话对象弹出键:
session.pop("KEY", None)
如果已登录的用户访问此路由,则其USERNAME
变量将从会话中删除,并将被重定向到登录页面。
我们通过None
来session.pop
确保未登录的用户是否访问了退出路由,他们也将被重定向到登录路由,而应用程序不会引发错误。
如果没有None
,则应用程序将引发KeyError
。
让我们测试一下我们的应用!
测试出来
登录之前,请在浏览器中打开开发人员工具并转到会话存储。
对于Chrome用户:
- 使用打开开发人员工具
Ctrl + Shift + i
- 选择
Application
工具栏顶部的标签 Cookies
从左侧的侧边栏中选择
对于Firefox用户:
- 使用打开开发人员工具
Ctrl + Shift + i
- 选择
Storage
工具栏顶部的标签 Cookies
从左侧的侧边栏中选择
转到/sign-in
,在数据库中输入用户名和密码之一,然后提交表单。
您应该看到一个cookie,它session
在值条目中显示了名称和编码字符串。
如果一切顺利,您将进入该用户的个人资料页面,并查看他们的一些详细信息。
现在,/sign-out
在浏览器中转到,您应该被重定向到登录页面。会话cookie也应该被删除,您将不再在开发人员工具中看到它。
会议最佳做法
如前所述,会话对象不是存储数据的安全场所,因为它很容易解码。
您存储在会话对象中的一些示例:
- 唯一的用户ID
- 公开可见的用户名
- 追踪编号
- 用户偏好
理想情况下,您将在数据库或本地缓存(如Redis)中存储尽可能多的内容。
切勿在会话对象中放置密码或任何敏感信息!正如我们现在将说明原因...
解码会话
重新登录该应用程序并打开开发人员工具。
在开发人员工具中,转到Network
选项卡,然后寻找对路线的POST
请求/sign-in
。点击它。
在下方Response headers
,您会看到Set-Cookie:
类似以下内容的值:
session=eyJVU0VSTkFNRSI6Imp1bGlhbiJ9.XGxnkw.0-dtOEX9rJYS9MqYgnM9reWr7dY; HttpOnly; Path=/
复制此字符串并在终端或控制台中启动Python解释器的实例。
python
导入base64:
>>> import base64
我们将传递会话takeie,base64.b64decode()
该会话要求字符串的字符长度不得超过4的倍数1。
您可能必须从字符串中删除一个字符或2个字符,直到获得正确数量的字符为止。
使用base64.b64decode("session_cookie")
以下命令解码字符串:
>>> base64.b64decode("eyJVU0VSTkFNRSI6Imp1bGlhbiJ9.XGxnkw.0-dtOEX9rJYS9MqYgnM9reWr7dY; HttpOnly; Path=")
在剥离session=
并/
从字符串中拖尾之后,我们得到以下输出:
b'{"USERNAME":"julian"}\\lg\x93\r\x1d\xb4\xe1\x17\xf6\xb2XK\xd3*b\t\xcc\xf6\xb7\x96\xaf\xb7X\x1e\xdbi:yr=\xaba'
如您所见,我们已经解码了字符串并显示了USERNAME
值。其余的只是填充。
我希望这说明了为什么您不应该在会话中存储任何敏感内容!
包起来
Flask中的会话对象是一种非常有用的工具,用于记住和共享应用程序之间的状态,应谨慎使用。
可以使用Jinja {{ session["KEY"] }}
语法在整个应用程序和模板中对其进行全局访问。
只要记住要设置一个秘密密钥并保持安全即可。理想情况下是在应用程序配置文件中,并且不受版本控制!
views.py
from flask import render_template, request, session, redirect, url_for
app.config["SECRET_KEY"] = "OCML3BRawWEUeaxcuKHLpw"
users = {
"julian": {
"username": "julian",
"email": "julian@gmail.com",
"password": "example",
"bio": "Some guy from the internet"
},
"clarissa": {
"username": "clarissa",
"email": "clarissa@icloud.com",
"password": "sweetpotato22",
"bio": "Sweet potato is life"
}
}
@app.route("/sign-in", methods=["GET", "POST"])
def sign_in():
if request.method == "POST":
req = request.form
username = req.get("username")
password = req.get("password")
if not username in users:
print("Username not found")
return redirect(request.url)
else:
user = users[username]
if not password == user["password"]:
print("Incorrect password")
return redirect(request.url)
else:
session["USERNAME"] = user["username"]
print(session)
print("session username set")
return redirect(url_for("profile"))
return render_template("public/sign_in.html")
@app.route("/profile")
def profile():
if not session.get("USERNAME") is None:
username = session.get("USERNAME")
user = users[username]
return render_template("public/profile.html", user=user)
else:
print("No username found in session")
return redirect(url_for("sign_in"))
@app.route("/sign-out")
def sign_out():
session.pop("USERNAME", None)
return redirect(url_for("sign_in"))
文章来源:https://pythonise.com/series/learning-flask/flask-session-object 机译
布施恩德可便相知重
微信扫一扫打赏
支付宝扫一扫打赏