介绍
允许用户登录您的应用程序是您将添加到 Web 应用程序中的最常见功能之一。 您可以使用 Flask-Login 包向 Flask 应用程序添加身份验证。
在本教程中,您将:
- 使用 Flask-Login 库进行会话管理
- 使用内置的 Flask 实用程序对密码进行哈希处理
- 仅为登录用户将受保护的页面添加到应用程序
- 使用 Flask-SQLAlchemy 创建
User
模型 - 为用户创建注册和登录表单以创建帐户和登录
- 出现问题时将错误消息闪回给用户
- 使用用户帐户中的信息显示在个人资料页面上
您将构建一个注册和登录页面,允许用户登录和访问受保护的页面。 您将使用来自 User
模型的信息,并在用户登录时将其显示在受保护的页面上,以模拟配置文件的外观。
注意: 本教程范围有限,不包括会话的高级持久化。 此外,修改主键的数据类型或迁移到不同数据库系统的注意事项也在本介绍性教程的范围之外。
该项目的 源代码可在 GitHub 上获得。
先决条件
要完成本教程,您将需要以下内容:
- 对 Python 有一定的了解。
- Python 安装在本地环境。
- 基本Linux导航和文件管理知识。
这是一个图表,可让您了解完成教程后项目的文件结构将是什么样子:
. └── flask_auth_app └── project ├── __init__.py # setup the app ├── auth.py # the auth routes for the app ├── db.sqlite # the database ├── main.py # the non-auth routes for the app ├── models.py # the user model └── templates ├── base.html # contains common layout and links ├── index.html # show the home page ├── login.html # show the login form ├── profile.html # show the profile page └── signup.html # show the signup form
随着您学习本教程的进展,您将创建这些目录和文件。
本教程已通过 sqlite3
v3.36.0、python
v3.9.8、flask
v2.0.2、flask-login
v0.5.0 和 [ X113X] v2.5.1。
第 1 步 — 安装软件包
您的项目需要三个主要包:
- 烧瓶
- Flask-Login:在认证后处理用户会话
- Flask-SQLAlchemy:表示用户模型和与数据库的接口
您将使用 SQLite 来避免为数据库安装任何额外的依赖项。
首先,从创建项目目录开始:
mkdir flask_auth_app
接下来,导航到项目目录:
cd flask_auth_app
如果您没有 Python 环境,您将需要创建一个。
注意:设置venv
可以参考本地环境相关教程。
根据您机器上 Python 的安装方式,您的命令将类似于:
python3 -m venv auth
-m
标志用于 module-name
。 该命令将执行模块 venv
以创建一个名为 auth
的新虚拟环境。 这将创建一个包含 bin
、include
和 lib
子目录的新目录。 还有一个 pyvenv.cfg
文件。
接下来,运行以下命令:
source auth/bin/activate
此命令将激活虚拟环境。
从您的虚拟环境运行以下命令以安装所需的软件包:
pip install flask flask-sqlalchemy flask-login
现在您已经安装了软件包,您可以创建主应用程序文件了。
第 2 步 — 创建主应用程序文件
让我们首先创建一个 project
目录:
mkdir project
第一个文件将是项目的 __init__.py
文件:
nano project/__init__.py
这个应用程序将使用带有蓝图的 Flask 应用程序工厂模式。 一个蓝图处理常规路由,其中包括索引和受保护的配置文件页面。 另一个蓝图处理与身份验证相关的所有内容。 在一个真实的应用程序中,您可以以任何您喜欢的方式分解功能,但这里介绍的解决方案适用于本教程。
该文件将具有创建应用程序的功能,该应用程序将初始化数据库并注册蓝图。 目前,这不会有太大作用,但应用程序的其余部分将需要它。
您将需要初始化 SQLAlchemy,设置一些配置值,并在此处注册蓝图:
项目/__init__.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy # init SQLAlchemy so we can use it later in our models db = SQLAlchemy() def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = 'secret-key-goes-here' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' db.init_app(app) # blueprint for auth routes in our app from .auth import auth as auth_blueprint app.register_blueprint(auth_blueprint) # blueprint for non-auth parts of app from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
现在您有了主应用程序文件,开始添加路由。
第 3 步 - 添加路由
对于路线,您将使用两个蓝图。
对于 main_blueprint
,您将拥有一个主页 (/
) 和一个配置文件页面 (/profile
)。
首先,创建 main.py
:
nano project/main.py
然后添加您的 main_blueprint
:
项目/main.py
from flask import Blueprint from . import db main = Blueprint('main', __name__) @main.route('/') def index(): return 'Index' @main.route('/profile') def profile(): return 'Profile'
对于 auth_blueprint
,您将拥有检索登录页面 (/login
) 和注册页面 (/signup
) 的路由。 最后,您将有一个注销路径 (/logout
) 来注销活动用户。
接下来,创建 auth.py
:
nano project/auth.py
然后添加您的 auth_blueprint
:
项目/auth.py
from flask import Blueprint from . import db auth = Blueprint('auth', __name__) @auth.route('/login') def login(): return 'Login' @auth.route('/signup') def signup(): return 'Signup' @auth.route('/logout') def logout(): return 'Logout'
暂时用文本返回定义login
、signup
和logout
。 您还将拥有处理来自 login
和 signup
的 POST 请求的路由。 稍后您将重新访问此代码并使用所需的功能对其进行更新。
在终端中,您可以设置 FLASK_APP
和 FLASK_DEBUG
值:
export FLASK_APP=project export FLASK_DEBUG=1
FLASK_APP
环境变量指示 Flask 如何加载应用程序。 您可能希望它指向 create_app
所在的位置。 对于本教程,您将指向 project
目录。
通过将 FLASK_DEBUG
环境变量设置为 1
来启用它。 这将启用将在浏览器中显示应用程序错误的调试器。
确保您在 flask_auth_app
目录中,然后运行项目:
flask run
现在,在 Web 浏览器中,您可以导航到五个可能的 URL,并查看在 auth.py
和 main.py
中定义的返回文本。
例如访问localhost:5000/profile
显示:Profile
:
一旦您确认路由的行为符合预期,您就可以创建模板。
第 4 步 — 创建模板
接下来,创建应用程序中使用的模板。 这是实现实际登录功能之前的第一步。
该应用程序将使用四个模板:
- 索引.html
- profile.html
- 登录.html
- 注册.html
您还将拥有一个基本模板,其中包含每个页面共有的代码。 在这种情况下,基本模板将具有导航链接和页面的总体布局。
首先,在project
目录下创建一个templates
目录:
mkdir -p project/templates
然后创建base.html
:
nano project/templates/base.html
接下来,将以下代码添加到 base.html
文件中:
项目/模板/base.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flask Auth Example</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" /> </head> <body> <section class="hero is-primary is-fullheight"> <div class="hero-head"> <nav class="navbar"> <div class="container"> <div id="navbarMenuHeroA" class="navbar-menu"> <div class="navbar-end"> <a href="{{ url_for('main.index') }}" class="navbar-item"> Home </a> <a href="{{ url_for('main.profile') }}" class="navbar-item"> Profile </a> <a href="{{ url_for('auth.login') }}" class="navbar-item"> Login </a> <a href="{{ url_for('auth.signup') }}" class="navbar-item"> Sign Up </a> <a href="{{ url_for('auth.logout') }}" class="navbar-item"> Logout </a> </div> </div> </div> </nav> </div> <div class="hero-body"> <div class="container has-text-centered"> {% block content %} {% endblock %} </div> </div> </section> </body> </html>
此代码将创建一系列指向应用程序每个页面的菜单链接。 它还为 content
建立了一个可以被子模板覆盖的块。
注意: 本教程使用 Bulma 来处理样式和布局。 要更深入地了解 Bulma,请考虑阅读 官方 Bulma 文档 。
接下来,创建 templates/index.html
:
nano project/templates/index.html
将以下代码添加到新创建的文件中以向页面添加内容:
项目/模板/index.html
{% extends "base.html" %} {% block content %} <h1 class="title"> Flask Login Example </h1> <h2 class="subtitle"> Easy authentication and authorization in Flask. </h2> {% endblock %}
此代码将创建一个带有标题和副标题的基本索引页面。
接下来,创建 templates/login.html
:
nano project/templates/login.html
此代码生成一个登录页面,其中包含 Email 和 Password 字段。 还有一个复选框可以“记住”已登录的会话。
项目/模板/login.html
{% extends "base.html" %} {% block content %} <div class="column is-4 is-offset-4"> <h3 class="title">Login</h3> <div class="box"> <form method="POST" action="/login"> <div class="field"> <div class="control"> <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus=""> </div> </div> <div class="field"> <div class="control"> <input class="input is-large" type="password" name="password" placeholder="Your Password"> </div> </div> <div class="field"> <label class="checkbox"> <input type="checkbox" name="remember"> Remember me </label> </div> <button class="button is-block is-info is-large is-fullwidth">Login</button> </form> </div> </div> {% endblock %}
接下来,创建 templates/signup.html
:
nano project/templates/signup.html
添加以下代码以创建包含 email
、name
和 password
字段的注册页面:
项目/模板/signup.html
{% extends "base.html" %} {% block content %} <div class="column is-4 is-offset-4"> <h3 class="title">Sign Up</h3> <div class="box"> <form method="POST" action="/signup"> <div class="field"> <div class="control"> <input class="input is-large" type="email" name="email" placeholder="Email" autofocus=""> </div> </div> <div class="field"> <div class="control"> <input class="input is-large" type="text" name="name" placeholder="Name" autofocus=""> </div> </div> <div class="field"> <div class="control"> <input class="input is-large" type="password" name="password" placeholder="Password"> </div> </div> <button class="button is-block is-info is-large is-fullwidth">Sign Up</button> </form> </div> </div> {% endblock %}
接下来,创建 templates/profile.html
:
nano project/templates/profile.html
添加此代码以创建一个带有硬编码标题以欢迎 Anthony 的页面:
项目/模板/profile.html
{% extends "base.html" %} {% block content %} <h1 class="title"> Welcome, Anthony! </h1> {% endblock %}
稍后您将重新访问此代码以动态问候任何用户。
添加模板后,您可以更新每个路由中的 return 语句以返回模板而不是文本。
接下来,通过修改 index
和 profile
的导入行和路由来更新 main.py
:
项目/main.py
from flask import Blueprint, render_template ... @main.route('/') def index(): return render_template('index.html') @main.route('/profile') def profile(): return render_template('profile.html')
现在您将通过修改 login
和 signup
的导入线路和路线来更新 auth.py
:
项目/auth.py
from flask import Blueprint, render_template ... @auth.route('/login') def login(): return render_template('login.html') @auth.route('/signup') def signup(): return render_template('signup.html')
完成这些更改后,如果您导航到 /signup
,注册页面如下所示:
您也可以导航到 /
、/login
和 /profile
的页面。
暂时不要使用 /logout
,因为它稍后不会显示模板。
第 5 步——创建用户模型
用户模型代表了应用程序拥有用户的意义。 本教程将需要 email
地址、password
和 name
的字段。 在未来的应用程序中,您可能决定要为每个用户存储更多信息。 您可以添加生日、个人资料图片、位置或任何用户偏好等内容。
在 Flask-SQLAlchemy 中创建的模型由类表示,然后转换为数据库中的表。 这些类的属性然后变成这些表的列。
创建 User
模型:
nano project/models.py
定义 User
模型:
项目/模型.py
from . import db class User(db.Model): id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy email = db.Column(db.String(100), unique=True) password = db.Column(db.String(100)) name = db.Column(db.String(1000))
此代码定义了一个 User
,其中包含 id
、email
、password
和 name
的列。
现在您已经创建了一个 User
模型,您可以继续配置您的数据库。
第 6 步 — 配置数据库
您将使用 SQLite 数据库。 你可以自己创建一个 SQLite 数据库,但让 Flask-SQLAlchemy 为你做这件事。 您已经在 __init__.py
文件中指定了数据库的路径,因此您需要告诉 Flask-SQLAlchemy 在 Python REPL 中创建数据库。
确保您仍然在虚拟环境中,并且在 flask_auth_app
目录中。
如果您停止应用程序并打开 Python REPL,您可以使用 db
对象上的 create_all
方法创建数据库:
from project import db, create_app, models db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.
注意:如果对Python解释器不熟悉,可以参考官方文档。
您现在将在项目目录中看到一个 db.sqlite
文件。 该数据库将包含用户表。
第 7 步 — 设置授权功能
对于注册功能,您将获取用户提交到表单的数据并将其添加到数据库中。 您需要确保数据库中不存在具有相同电子邮件地址的用户。 如果它不存在,那么您需要确保在将密码放入数据库之前对密码进行哈希处理。
注意: 以明文形式存储密码被认为是一种糟糕的安全做法。 您通常需要一个复杂的散列算法和盐来保证密码的安全。
让我们首先添加第二个函数来处理 POST 表单数据。 收集用户传递的数据。
通过修改导入行并实现 signup_post
来更新 auth.py
:
项目/auth.py
from flask import Blueprint, render_template, redirect, url_for ... @auth.route('/signup') def signup(): return render_template('signup.html') @auth.route('/signup', methods=['POST']) def signup_post(): # code to validate and add user to database goes here return redirect(url_for('auth.login'))
创建函数并添加重定向。 这将提供成功注册并被定向到登录页面的用户体验。
现在,让我们添加注册用户所需的其余代码。 使用请求对象获取表单数据。
通过添加导入和实现 signup_post
继续更新 auth.py
:
授权文件
from flask import Blueprint, render_template, redirect, url_for, request from werkzeug.security import generate_password_hash, check_password_hash from .models import User from . import db ... @auth.route('/signup', methods=['POST']) def signup_post(): # code to validate and add user to database goes here email = request.form.get('email') name = request.form.get('name') password = request.form.get('password') user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database if user: # if a user is found, we want to redirect back to signup page so user can try again return redirect(url_for('auth.signup')) # create a new user with the form data. Hash the password so the plaintext version isn't saved. new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256')) # add the new user to the database db.session.add(new_user) db.session.commit() return redirect(url_for('auth.login'))
此代码将检查数据库中是否存在具有相同电子邮件地址的用户。
第 8 步 — 测试注册方法
现在您已经完成了注册方法,您将能够创建一个新用户。 让我们测试表单以创建用户。
有两种方法可以验证注册是否成功:
- 您可以使用数据库查看器查看添加到表中的行。
- 或者您可以尝试再次使用相同的电子邮件地址注册,如果出现错误,您就知道第一封电子邮件已正确保存。
让我们添加代码让用户知道电子邮件已经存在并引导他们转到登录页面。 通过调用 flash
函数,您可以将消息发送到下一个请求,在本例中为重定向。 然后,用户被重定向到的页面将有权访问模板中的该消息。
首先,在重定向到注册页面之前添加 flash
。
项目/auth.py
from flask import Blueprint, render_template, redirect, url_for, request, flash ... @auth.route('/signup', methods=['POST']) def signup_post(): ... if user: # if a user is found, we want to redirect back to signup page so user can try again flash('Email address already exists') return redirect(url_for('auth.signup'))
要在模板中获取闪现的消息,您可以在表单之前添加此代码。
项目/模板/signup.html
... {% with messages = get_flashed_messages() %} {% if messages %} <div class="notification is-danger"> {{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>. </div> {% endif %} {% endwith %} <form method="POST" action="/signup">
如果电子邮件地址已在数据库中,此代码将显示消息 "Email address already exists. Go to login page."
。
此时,您可以运行应用程序并尝试使用已经存在的电子邮件地址进行注册。
第 9 步 — 添加登录方法
登录方法类似于注册功能。 在这种情况下,您将比较输入的 email
地址以查看它是否在数据库中。 如果是这样,您将通过对用户传入的 password
进行散列并将其与数据库中的散列 password
进行比较来测试用户提供的 password
。 当两个散列的 password
匹配时,您将知道用户输入了正确的 password
。
用户通过密码检查后,您将知道他们拥有正确的凭据,您可以使用 Flask-Login 登录。 通过调用 login_user
,Flask-Login 将为该用户创建一个会话,该会话将在用户保持登录状态时持续存在,这将允许用户查看受保护的页面。
您可以从处理 POST 提交的数据的新路由开始。 并在用户成功登录后重定向到个人资料页面:
项目/auth.py
... @auth.route('/login') def login(): return render_template('login.html') @auth.route('/login', methods=['POST']) def login_post(): # login code goes here return redirect(url_for('main.profile'))
现在,您需要验证用户是否拥有正确的凭据:
项目/auth.py
... @auth.route('/login', methods=['POST']) def login_post(): # login code goes here email = request.form.get('email') password = request.form.get('password') remember = True if request.form.get('remember') else False user = User.query.filter_by(email=email).first() # check if the user actually exists # take the user-supplied password, hash it, and compare it to the hashed password in the database if not user or not check_password_hash(user.password, password): flash('Please check your login details and try again.') return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page # if the above check passes, then we know the user has the right credentials return redirect(url_for('main.profile'))
让我们在模板中添加块,以便用户可以看到闪烁的消息:
项目/模板/login.html
... {% with messages = get_flashed_messages() %} {% if messages %} <div class="notification is-danger"> {{ messages[0] }} </div> {% endif %} {% endwith %} <form method="POST" action="/login">
您现在可以说用户已成功登录,但没有任何内容可以让用户登录。
Flask-Login 可以管理用户会话。 首先将 UserMixin
添加到您的用户模型。 UserMixin
会将 Flask-Login 属性添加到模型中,以便 Flask-Login 能够使用它。
模型.py
from flask_login import UserMixin from . import db class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy email = db.Column(db.String(100), unique=True) password = db.Column(db.String(100)) name = db.Column(db.String(1000))
然后,您需要指定 用户加载程序。 用户加载器告诉 Flask-Login 如何从存储在会话 cookie 中的 ID 中找到特定用户。 在 create_app
函数中添加这个以及 Flask-Login 的 init
代码:
项目/__init__.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager ... def create_app(): ... db.init_app(app) login_manager = LoginManager() login_manager.login_view = 'auth.login' login_manager.init_app(app) from .models import User @login_manager.user_loader def load_user(user_id): # since the user_id is just the primary key of our user table, use it in the query for the user return User.query.get(int(user_id))
最后,在重定向到个人资料页面之前添加 login_user
函数以创建会话:
项目/auth.py
from flask_login import login_user from .models import User from . import db ... @auth.route('/login', methods=['POST']) def login_post(): ... # if the above check passes, then we know the user has the right credentials login_user(user, remember=remember) return redirect(url_for('main.profile'))
使用 Flask-Login 设置,使用 /login
路由。 一切就绪后,您将看到个人资料页面。
此时,您可以运行应用程序并尝试登录。
第 10 步 - 保护页面
如果您的名字不是 Anthony,那么您会在个人资料页面上看到您的名字是错误的。 目标是让配置文件在数据库中显示名称。 您需要保护页面,然后访问用户的数据以获取 name
。
要在使用 Flask-Login 时保护页面,请在路由和函数之间添加 @login_requried
装饰器。 这将防止未登录的用户看到该路线。 如果用户未登录,则根据 Flask-Login 配置,用户将被重定向到登录页面。
对于使用 @login_required
装饰器装饰的路由,您可以在函数内部使用 current_user
对象。 这个 current_user
代表数据库中的用户,并使用 点符号 提供对该用户所有属性的访问。 例如,current_user.email
、current_user.password
和 current_user.name
和 current_user.id
将为登录用户返回存储在数据库中的实际值。
让我们使用 current_user
的 name
并将其发送到模板:
项目/main.py
from flask import Blueprint, render_template from flask_login import login_required, current_user from . import db ... @main.route('/profile') @login_required def profile(): return render_template('profile.html', name=current_user.name)
然后在 profile.html
文件中,更新页面以显示 name
值:
项目/模板/profile.html
... <h1 class="title"> Welcome, {{ name }}! </h1>
一旦用户访问个人资料页面,他们将受到他们的 name
的欢迎。
现在要更新注销视图,请在注销路由中调用 logout_user
函数:
项目/auth.py
from flask_login import login_user, login_required, logout_user ... @auth.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('main.index'))
使用 @login_required
装饰器,因为注销一个未登录的用户是没有意义的。
在用户注销并尝试再次查看个人资料页面后,他们将看到一条错误消息:
这是因为当不允许用户访问页面时,Flask-Login 会闪烁一条消息。
最后一件事是将 if
语句放入模板中以仅显示与用户相关的链接:
模板/base.html
... <div class="navbar-end"> <a href="{{ url_for('main.index') }}" class="navbar-item"> Home </a> {% if current_user.is_authenticated %} <a href="{{ url_for('main.profile') }}" class="navbar-item"> Profile </a> {% endif %} {% if not current_user.is_authenticated %} <a href="{{ url_for('auth.login') }}" class="navbar-item"> Login </a> <a href="{{ url_for('auth.signup') }}" class="navbar-item"> Sign Up </a> {% endif %} {% if current_user.is_authenticated %} <a href="{{ url_for('auth.logout') }}" class="navbar-item"> Logout </a> {% endif %} </div>
在用户登录之前,他们可以选择登录或注册。 登录后,他们可以转到他们的个人资料或注销。
这样,您就成功地构建了具有身份验证的应用程序。
结论
在本教程中,您使用 Flask-Login 和 Flask-SQLAlchemy 为应用程序构建了登录系统。 您介绍了如何通过首先创建用户模型并存储用户信息来对用户进行身份验证。 然后,您必须通过对表单中的密码进行哈希处理并将其与存储在数据库中的密码进行比较来验证用户的密码是否正确。 最后,您通过在个人资料页面上使用 @login_required
装饰器为应用程序添加了授权,因此只有登录用户才能看到该页面。
您在本教程中创建的内容对于较小的应用程序来说已经足够了,但是如果您希望从一开始就拥有更多功能,您可能需要考虑使用 Flask-User 或 Flask-Security 库,它们都构建在烧瓶登录库。