Flask----前后端不分离-登录

文章目录

  • 扩展模块
  • 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方式

  1. 设置cookie
# 在服务端返回响应时,设置cookie
res = render_template("index.html")
res.set_cookie("uid", str(user.id), max_age=3600)  # max_age 过期时间秒
return res
  1. 验证cookie
# 识别用户的登录,通过请求上下文对象request来获取
uid = request.cookies.get("uid")
if uid:
	# 查询用户对象
	user = UserModel.query.filter_by(id=uid).first()
	if user:
		# 已登录
		return render_template("index.html")
  1. 删除cookie
# 退出登录时,删除cookie
res = redirect(url_for("login"))
res.delete_cookie("uid")
return res
  1. flask中生成响应对象
res = Response("xxxx")
res = make_response("xxxx")
res = jsonify({})
res = redirect(xxx)
res = render_template("index.html")

session方式

  1. 设置方式
from flask import session  # 请求上下文对象

# 存储
session["uid"] = user.uid
return render_template("index.html")

  1. 验证方式
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")
  1. 删除方式
del session['uid']
# 或者
session.pop("uid", None)
  1. 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扩展,它处理登录、注销和长时间记住用户会话;
  • 提供对用户会话的管理;
  • 简化用户的登录、登出、登录检查;
  • 使用方式如下:
  1. 实例化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变量即可

  1. 定义模型类混入
    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))
	
  1. 视图中处理登入、登出
    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)
	

 

代码目录

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/953028.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot + vue+elementUI图片上传流程

1.实现背景 前端上传一张图片&#xff0c;存到后端数据库&#xff0c;并将图片回显到页面上。上传组件使用现成的elementUI的el-upload。、 2.前端页面 <el-uploadclass"upload-demo"action"http://xxxx.xxx.xxx:9090/file/upload" :show-file-list&q…

深度学习张量的秩、轴和形状

深度学习张量的秩、轴和形状 秩、轴和形状是在深度学习中我们最关心的张量属性。 秩轴形状 秩、轴和形状是在深度学习中开始使用张量时我们最关心的三个属性。这些概念相互建立&#xff0c;从秩开始&#xff0c;然后是轴&#xff0c;最后构建到形状&#xff0c;所以请注意这…

Observability:将 OpenTelemetry 添加到你的 Flask 应用程序

作者&#xff1a;来自 Elastic jessgarson 待办事项列表可以帮助管理与假期计划相关的所有购物和任务。使用 Flask&#xff0c;你可以轻松创建待办事项列表应用程序&#xff0c;并使用 Elastic 作为遥测后端&#xff0c;通过 OpenTelemetry 对其进行监控。 Flask 是一个轻量级…

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(五)

文章目录 一、学生管理模块功能实现1、添加学生功能实现1.1 页面设计1.2 前端功能实现1.3 后端功能实现1.4 效果展示2、学生管理功能实现2.1 页面设计2.2 前端功能实现2.3 后端功能实现2.3.1 后端查询接口实现2.3.2 后端编辑接口实现2.3.3 后端删除接口实现2.4 效果展示二、代码…

使用Cilium/eBPF实现大规模云原生网络和安全

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 目录 抽象 1 Trip.com 云基础设施 1.1 分层架构 1.2 更多细节 2 纤毛在 Trip.com 2.1 推出时间表 2.2 自定义 2.3 优化和调整 2.3.1 解耦安装 2.3.2 避免重试/重启风暴 2.3.3 稳定性优先 2…

CTFshow—文件包含

Web78-81 Web78 这题是最基础的文件包含&#xff0c;直接?fileflag.php是不行的&#xff0c;不知道为啥&#xff0c;直接用下面我们之前在命令执行讲过的payload即可。 ?filephp://filter/readconvert.base64-encode/resourceflag.php Web79 这题是过滤了php&#xff0c;…

62.在 Vue 3 中使用 OpenLayers 设置不同的坐标点,用不同的颜色区分

前言 在现代 Web 开发中&#xff0c;地图功能已经成为许多应用的重要组成部分。OpenLayers 是一个强大的开源地图库&#xff0c;支持多种地图源和地图操作。结合 Vue 3 的响应式特性&#xff0c;我们可以轻松实现地图的交互功能。本文将详细介绍如何在 Vue 3 中使用 OpenLayer…

Spring 项目 基于 Tomcat容器进行部署

文章目录 一、前置知识二、项目部署1. 将写好的 Spring 项目先打包成 war 包2. 查看项目工件&#xff08;Artifact&#xff09;是否存在3. 配置 Tomcat3.1 添加一个本地 Tomcat 容器3.2 将项目部署到 Tomcat 4. 运行项目 尽管市场上许多新项目都已经转向 Spring Boot&#xff0…

【学习笔记】数据结构(十一)

外部排序 文章目录 外部排序11.1 外存信息的存取11.2 外部排序的方法11.3 多路平衡归并的实现 - 增加k11.4 置换-选择排序 - 减少m11.5 最佳归并树 外部排序 指的是大文件的排序&#xff0c;即待排序的记录存储在外存储器 上&#xff0c;在排序过程中需进行多次的内、外存之间的…

《跟我学Spring Boot开发》系列文章索引❤(2025.01.09更新)

章节文章名备注第1节Spring Boot&#xff08;1&#xff09;基于Eclipse搭建Spring Boot开发环境环境搭建第2节Spring Boot&#xff08;2&#xff09;解决Maven下载依赖缓慢的问题给火车头提提速第3节Spring Boot&#xff08;3&#xff09;教你手工搭建Spring Boot项目纯手工玩法…

【Linux笔记】Day1

基于韩顺平老师课程记录&#xff1a; https://www.bilibili.com/video/BV1Sv411r7vd 安装CentOS 给CentOS手动分区 分为三个区&#xff1a; boot分区&#xff08;给1G就行&#xff09; 交换分区&#xff08;和内存相关&#xff0c;这里和虚拟机的内存2G一致&#xff09; …

【网络】:网络编程套接字

目录 源IP地址和目的IP地址 源MAC地址和目的MAC地址 源端口号和目的端口号 端口号 VS 进程ID TCP协议和UDP协议 网络字节序 字符串IP和整数IP相互转换 查看当前网络的状态 socket编程接口 socket常见API 创建套接字 绑定端口号 发送数据 接收数据 sockaddr结构…

使用 Multer 上传图片到阿里云 OSS

文件上传到哪里更好&#xff1f; 上传到服务器本地 上传到服务器本地&#xff0c;这种方法在现今商业项目中&#xff0c;几乎已经见不到了。因为服务器带宽&#xff0c;磁盘 IO 都是非常有限的。将文件上传和读取放在自己服务器上&#xff0c;并不是明智的选择。 上传到云储存…

【端云一体化】云函数的使用

前言 为丰富HarmonyOS对云端开发的支持、实现端云联动&#xff0c;DevEco Studio以Cloud Foundation Kit&#xff08;云开发服务&#xff09;为底座、在传统的“端开发”基础上新增“云开发”能力&#xff0c;开发者在创建工程时选择合适的云开发工程模板&#xff0c;即可在De…

YARN 架构组件及原理

一、YARN 体系架构 YARN&#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09; 是 Hadoop 2.0 中的资源管理系统&#xff0c;它的基本设计思想是将 MRv1 中的 JobTracker拆分成了两个独立的服务 &#xff1a;一个全局的资源管理器 ResourceMa…

C# GDI+的DrawString无法绘制Tab键的现象

【啰嗦2句】 现在用C#的人很少了吧&#xff1f;GDI更少了吧&#xff1f;所以这个问题估计也冷门。没关系&#xff0c;分享给特定需要的人也不错。 【问题现象】 工作中开发了一个报告编辑器&#xff0c;实现图文排版等功能&#xff0c;用着没什么问题&#xff0c;直到有一天…

最近在盘gitlab.0.先review了一下docker

# 正文 本猿所在产品的代码是保存到了一个本地gitlab实例上&#xff0c;实例是别的同事搭建的。最近又又又想了解一下&#xff0c;而且已经盘了一些了&#xff0c;所以写写记录一下。因为这个事儿没太多的进度压力&#xff0c;索性写到哪儿算哪儿&#xff0c;只要是新了解到的…

春秋云镜——initial

初步认识内网渗透流程 thinkphp外网打点 打开环境后尝试登陆无果&#xff0c;用fscan扫一下看看 fscan.exe -h 39.99.224.87 发现是think PHP漏洞 补充&#xff1a; fscan&#xff1a;一款内网综合扫描工具&#xff0c;方便一键自动化、全方位漏扫扫描。支持主机存活探测、端…

【C++】string的关系运算与比较分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;基础知识&#xff1a;C 中的 string 关系运算器1. 关系运算器概述2. 字符串比较的本质 &#x1f4af;代码解析与扩展代码例一&#xff1a;相等比较代码解析输出 代码例二&a…

Qt C++读写NFC标签NDEF网址URI

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1biFgjSs&ftt&id615391857885 #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> #include "QLibrary" …