文章目录
- 扩展模块
- flask-wtf 的简单使用
- 定义用户数据模型
- 注册与登录
- 会话保持
- cookie方式
- session方式
- 基于session的登录
- flask-login实现登录、登出
- 代码目录
扩展模块
- flask-sqlalchmy,连接数据库
- flask-login,处理用户的登录,认证
- flask-session,会话保持,默认对用户数据加密,存储在客户端浏览器的cookie中,每次请求时携带cookie来识别用户;也可以存储在服务端的文件、数据库、缓存中;
- flask-wtf 处理表单数据,防止csrf攻击;flask-wtf文档
flask-wtf 的简单使用
基于flask-wtf 制作简单的注册、登录页面;
- 注册页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<script src="/static/js/index.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
<form method="POST" action="/reg">
{{ form.csrf_token }}<br>
<h3>欢迎注册</h3><br>
{{ form.uname.label }} {{ form.uname(size=20) }}<br>
{{ form.passwd.label }} {{ form.passwd(size=20) }}<br>
{{ form.confirm_passwd.label }} {{ form.confirm_passwd(size=20)}}<br>
<input type="submit" value="登录">
{% if form.errors %}
<ul class="errors">
{% for error in form.errors %}
<li>{{ error }}字段验证未通过</li>
{% endfor %}
</ul>
{% endif %}
</form>
</body>
</html>
这里的form变量是flask渲染模板时,传入的表单对象。,form.csrf_token分别在表单中、cookie中生成一个秘钥,在提交表单时,cookie中的秘钥连同表单中的秘钥一同传给后端进行验证,验证通过则为合法的请求。
登录页面实现类似;
- flask后端定义表单子类、字段、验证器,app/_init_.py
# __author__ = "laufing"
import os
from flask import Flask
from .config import BaseConfig
app = Flask(__name__)
app.config.from_object(BaseConfig)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 模板地址
app.template_folder = os.path.join(BASE_DIR, "templates")
# 静态资源地址
app.static_url_path = "/static/"
app.static_folder = os.path.join(BASE_DIR, "static")
# 导入表单
from flask_wtf import FlaskForm
# 导入字段
from wtforms import StringField, PasswordField, IntegerField, DateTimeField, BooleanField
from wtforms.validators import DataRequired, InputRequired # 必须输入
from wtforms.validators import EqualTo
class RegForm(FlaskForm):
uname = StringField("uname", validators=[InputRequired(), DataRequired()])
passwd = PasswordField("passwd", validators=[DataRequired(), InputRequired()])
confirm_passwd = PasswordField("confirm_passwd", validators=[EqualTo("passwd")])
class LoginForm(FlaskForm):
# 在服务端验证用户的输入
uname = StringField("uname", validators=[DataRequired(), InputRequired()])
passwd = PasswordField("passwd", validators=[DataRequired(), InputRequired()])
- flask后端定义视图,main.py
# __author__ = "laufing"
from app import app
from flask import render_template, jsonify, request, session, redirect, url_for
from app import LoginForm, RegForm
@app.route("/reg", methods=["GET", "POST"])
def register():
if request.method == "GET":
form = RegForm()
return render_template("reg.html", form=form) # 传入form对象渲染表单
form = RegForm() # 接收request.form 表单数据
if form.validate_on_submit():
# 保存用户的信息,待实现
return redirect('/login') # 重定向到 /login GET
return render_template("reg.html", form=form)
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "GET": # 返回登录页面
return render_template("login.html", form=LoginForm())
# 实例化表单对象(自动接收request.form里的数据), 接收用户的登录数据
form = LoginForm()
# 按添加的验证器,进行验证数据 & 是否POST请求
if form.validate_on_submit():
print(form.uname.data)
print(form.passwd.data)
return jsonify({
"code": 200,
"msg": "登录成功"
})
# 验证未通过时,显示错误信息
return render_template("login.html", form=form)
if __name__ == '__main__':
app.run(host="localhost", port=5050, debug=True)
- 目录结构
定义用户数据模型
- 数据库mysql;
- 驱动 flask-sqlalchmy
- 迁移flask-migrate
- flask db init 初始化,生成迁移目录(仅一次);
- flask db migrate -m “描述”, 创建迁移脚本;
- flask db upgrade,应用迁移,创建数据库、表;
- flask db downgrade,降级
- flask db history,迁移历史
- 文件models/user_models.py
- 版本
- flask==2.0.3
- jinja2=3.1.1
- werkzeug == 2.0.3
- sqlalchemy==1.3
- flask-sqlalchemy==2.4.0
- pyjwt==2.0.0
- email-validator==1.0.5
- packaging==21.0
- flask-migrate==2.6.0
在窗口app对象的_init_.py文件中,添加如下:
from flask-sqlalchemy import SQLAlchemy
from flask-migrate import Migrate
# ...
db = SQLAlchemy()
db.init_app(app)
migrate = Migrate(app, db)
创建models目录/ user_models.py:
from app import db
class UserModel(db.Model):
__tablename__ = "user_t"
# 必须设定主键
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uname = db.Column(db.String(50), unique=True)
passwd = db.Column(db.String(64), unique=True)
def __repr__(self):
return self.uname
命令行下完成初始化、迁移:
flask db init
flask db migrate -m '生成迁移命令'
flask db upgrade
注册与登录
将注册登录的后端逻辑迁移到指定的模块,窗口routers目录及routers.py模块,仅为了便于管理路由与视图。
routers/routers.py 路由与视图函数:
# __author__ = "laufing"
# 注册路由与视图
from app import app, RegForm, LoginForm, db
from flask import request, session, render_template, send_file # request & session 请求上下文对象
from models.user_models import UserModel
from werkzeug.security import generate_password_hash, check_password_hash
@app.route("/user/reg", methods=["GET", "POST"]) # 必须从/开始
def reg():
if request.method == "GET":
# 实例化表单对象
form_obj = RegForm()
# 渲染注册页面
render_template("reg.html", form_obj=form_obj)
# 实例化表单,接收request.form里的数据
reg_form = RegForm()
if reg_form.validate_on_submit(): # 验证通过
# 保存用户的注册信息
user = UserModel(uname=reg_form.uname.data, password=generate_password_hash(reg_form.passwd.data))
# 通过会话保存
db.session.add(user)
db.session.commit()
# 返回登录页面
login_form = LoginForm()
return render_template("login.html", form_obj=login_form)
# 返回错误信息
return render_template("reg.html", form_obj=reg_form)
@app.route("/user/login", methods=["GET", "POST"]) # 必须从/开始
def login():
if request.method == "GET":
# 实例化表单对象
form_obj = LoginForm()
# 渲染注册页面
render_template("login.html", form_obj=form_obj)
# 实例化表单,接收request.form里的数据
login_form = LoginForm()
if login_form.validate_on_submit(): # 验证通过
# 验证用户的信息
uname = login_form.uname.data
passwd = login_form.passwd.data
# 查询用户
user = UserModel.query.filter_by(uname=uname).first()
if user and check_password_hash(user.password, passwd):
print("登录成功:", login_form.uname.data)
# 会话保持
session["uname"] = user.uname
# 返回首页
return render_template("index.html")
else:
login_form.custom_error = ["用户名或者密码错误"]
return render_template("login.html", form_obj=login_form)
# 返回错误信息
return render_template("login.html", form_obj=login_form)
会话保持
- HTTP是一种无状态的协议,即每次请求都是独立的,服务器无法识别不同请求之间的关联性,无法记录与用户的会话状态;如用户登录认证后,下次请求可能还需要重新登陆认证,造成用户体验非常不好;
- 会话保持机制则通过在客户端和服务器之间维护一个会话标识,使得服务器可以识别并保持与客户端的连接状态;
- 会话保持的方式
- cookie
- session
- token
- cookie,登录的用户认证通过后,通过响应res对象来set_cookie(key, value, max_age=300, expires=“xxx”)设置cookie信息,并key-val形式按域隔离存储在客户端浏览器中(大小受限、不够安全),后续的请求每次携带cookie信息,服务端通过cookie识别用户已登录认证;
- session,用户认证通过后在服务器端存储用户的信息,并生成一个session id 返回给客户端浏览器,存储在cookie中,后续请求携带cookie中的session id 来让服务端识别会话状态;flask中的session数据是通过SECRET_KEY加密后存储在客户端浏览器的cookie中,每次请求携带cookie中的session实现自动识别会话状态;
- token,一般在前后端分离的项目中使用jwt token来会话保持;
cookie方式
- 设置cookie
# 在服务端返回响应时,设置cookie
res = render_template("index.html")
res.set_cookie("uid", str(user.id), max_age=3600) # max_age 过期时间秒
return res
- 验证cookie
# 识别用户的登录,通过请求上下文对象request来获取
uid = request.cookies.get("uid")
if uid:
# 查询用户对象
user = UserModel.query.filter_by(id=uid).first()
if user:
# 已登录
return render_template("index.html")
- 删除cookie
# 退出登录时,删除cookie
res = redirect(url_for("login"))
res.delete_cookie("uid")
return res
- flask中生成响应对象
res = Response("xxxx")
res = make_response("xxxx")
res = jsonify({})
res = redirect(xxx)
res = render_template("index.html")
session方式
- 设置方式
from flask import session # 请求上下文对象
# 存储
session["uid"] = user.uid
return render_template("index.html")
- 验证方式
uid = session.get("uid") # flask 底层自动从request.cookies.get("session")获取加密数据,自动验证
if uid:
user = UserModel.query.filter_by(id=uid).first()
if user:
# 已登录认证
return render_template("index.html")
- 删除方式
del session['uid']
# 或者
session.pop("uid", None)
- session的配置
# flask的配置类
class BaseConfig:
# 连接uri
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://lauf:lauf123@localhost:3306/world"
# flask-sqlalchemy追踪模型对象的修改,并且发送信号,需要额外的内存
SQLALCHEMY_TRACE_MODIFICATIONS = False
# 日志输出,用于调试
SQLALCHEMY_ECHO = True
HOST = "localhost"
PORT = 5050
DEBUG = True
# 加密的秘钥,如session加密
SECRET_KEY = "abcxx23"
# session数据存储在Cookie中的key
SESSION_COOKIE_NAME = "session_lauf"
# session过期时间
PERMANENT_SESSION_LIFETIME = 3600
基于session的登录
@app.route("/user/login", methods=["GET", "POST"]) # 必须从/开始
def login():
if request.method == "GET":
# 实例化表单对象
form_obj = LoginForm()
# 渲染注册页面
render_template("login.html", form_obj=form_obj)
# 实例化表单,接收request.form里的数据
login_form = LoginForm()
if login_form.validate_on_submit(): # 验证通过
# 验证用户的信息
uname = login_form.uname.data
passwd = login_form.passwd.data
# 查询用户
user = UserModel.query.filter_by(uname=uname).first()
if user and check_password_hash(user.password, passwd):
print("登录成功:", login_form.uname.data)
# 会话保持
session["uid"] = user.id
# 返回首页
return render_template("index.html")
else:
login_form.custom_error = ["用户名或者密码错误"]
return render_template("login.html", form_obj=login_form)
# 返回错误信息
return render_template("login.html", form_obj=login_form)
# 首页
@app.route("/index", methods=["GET"])
def index():
# 已登录,返回首页
uid = session["uid"] # 在获取uid时,flask底层从request.cookies.get("session_lauf")获取session数据,并解码
if uid:
# 查询用户对象
user = UserModel.query.filter_by(id=uid).first()
if user:
return render_template("index.html", user=user)
# 未登录,返回登录页面
login_form = LoginForm()
return render_template("login.html", form_obj=login_form)
# 退出登录
@app.route("/user/logout", methods=["GET"])
def logout():
session.pop("uid", None)
# 或者del session["uid"]
login_form = LoginForm()
return render_template("login.html", form_obj=login_form)
flask-login实现登录、登出
- Flask-Login是一个Flask扩展,它处理登录、注销和长时间记住用户会话;
- 提供对用户会话的管理;
- 简化用户的登录、登出、登录检查;
- 使用方式如下:
- 实例化LoginManager对象
# 在 app/__init__.py 中实例化LoginManager
# app = Flask(__name__)
# 会话管理
login_manager = LoginManager()
login_manager.init_app(app) # app需要配置SECRET_KEY, 基于session实现会话保持; 同时login_manager会放置在current_app身上
login_manager.session_protection = 'strong' # 设置会话保护模式为强模式
login_manager.login_view = "login_view" # 若login_required验证不通过,则定向到登录视图 (不指定时,则会提示401 Unauthorized) 通过endpoint参数指定视图的名称;
login_manager.login_message = "请登录" # 需要自己在登录的模板中处理显示
# login_required验证登录失败时,会重定向到login视图,在该视图中渲染模板并传入login_message信息
# if request.method == "GET":
# 实例化表单对象
# form_obj = LoginForm()
# login_message = current_app.login_manager.login_message if 'next' in request.args else None
# print("登录失败:", request.args)
# 渲染注册页面
# return render_template("login.html", form_obj=form_obj, login_message=login_message)
# 在模板中处理login_message变量即可
- 定义模型类混入
models/user_models.py中修改模型类定义。
from app import db, login_manager
from flask_login import UserMixin
class UserModel(db.Model, UserMixin): # 继承UserMixin
# ...其他不变
# 定义用户对象加载
@login_manager.user_loader
def loader_user(uid):
return UserModel.query.get(int(uid))
- 视图中处理登入、登出
routers/routers.py
# 基于flask-login的登录
from flask_login import login_user, logout_user, login_required, current_user
# login_required 保护视图,必须登录才可以访问
# current_user 是flask-login提供的一个全局变量,在login_required保护的视图中获取当前登录的用户对象
# current_user.is_authenticated(检查用户是否已认证)、is_active(检查用户是否处于活动状态)、is_anonymous 是否匿名用户
# 登录认证成功,保持会话 (在以上代码中设置session的地方,替换为如下)
login_user(user_obj)
# 登出, 不传参
logout_user()
# 保护视图
@app.route("/index", methods=["GET"], endpoint="index") # endpoint为视图的名称,用户url_for 逆向解析
@login_required
def index():
return render_template("index.html", user=current_user)