flask中的flask-login
在 Flask 中,用户认证通常是通过使用扩展库(例如 Flask-Login、Flask-HTTPAuth 或 Flask-Security)来实现的。
本文详细地解释下 Flask 中的用户认证。这里是用 Flask-Login 插件为例,这是一个处理用户会话的插件。首先,你需要安装 Flask-Login。你可以使用 pip 来安装:
pip install flask-login
接下来,在你的应用中初始化 Flask-Login:
from flask_login import LoginManager
login_manager = LoginManager()
然后,在创建 Flask 应用实例之后,你需要初始化 LoginManager:
app = Flask(__name__)
login_manager.init_app(app)
Flask-Login 要求你实现一个 callback 函数,这个函数接受一个用户 ID,并返回相应的用户对象。这可以通过 user_loader
装饰器来实现:
from your_model_file import User
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
这里,User
是用户模型,它应该是一个数据库模型,用来存储用户信息。在 Flask 中,常常使用 SQLAlchemy 来管理数据库。一个简单的 User
模型可能如下所示:
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
password_hash = db.Column(db.String(128))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
此处,我们还使用了 Werkzeug 提供的密码散列方法,为了保护用户的密码。
接下来,你需要创建一个登录页面。登录页面需要验证用户输入的用户名和密码:
from flask import render_template, redirect, url_for, flash
from flask_login import login_user
from your_form_file import LoginForm
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user)
return redirect(url_for('index'))
return render_template('login.html', form=form)
这里的 LoginForm
是一个 Flask-WTF 表单类,用来处理表单数据。
最后,你需要为用户登出设置路由:
from flask_login import logout_user
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
在理解 Flask-Login 的执行流程之前,我们先需要理解几个关键的 Flask-Login 组件:
- LoginManager:它是 Flask-Login 的核心,负责管理用户会话。
- UserMixin:它为用户类提供默认的实现,如
is_authenticated
,is_active
,is_anonymous
和get_id
。 - user_loader:这是一个回调函数,当 Flask-Login 需要知道特定用户的信息时,会调用这个函数。这个函数应该接收一个用户 ID 作为输入,返回相应的用户对象。
- login_user:当用户在登录表单中提供了有效的凭证时,应用会调用这个函数,它会在用户会话中记录下该用户已登录。
- logout_user:当用户想要退出时,应用会调用这个函数,它会从用户会话中删除登录信息。
现在,我们来看一下 Flask-Login 的工作流程:
- 用户访问网站,发送一个请求到服务器。
- Flask 应用接收到请求,检查这个请求的会话中是否有用户 ID,如果有,那么 Flask-Login 会用这个 ID 调用
user_loader
回调函数,试图加载这个用户。 - 如果
user_loader
回调函数找到了用户,那么这个请求就是已认证的。如果没有找到用户,那么这个请求就是匿名的。 - 对于需要用户登录才能访问的视图函数,Flask-Login 会检查用户是否已认证。如果用户已认证,那么就允许访问。如果用户未认证,那么就重定向到登录页面。
- 在登录页面,用户会提供登录凭证,比如用户名和密码。服务器验证凭证,如果凭证有效,那么就调用
login_user
函数,记录用户已登录。 - 用户在浏览网站时,所有的请求都会带有用户的会话信息,所以服务器能知道是哪个用户发送的请求。
- 当用户决定退出时,应用会调用
logout_user
函数,删除用户的会话信息。
这就是 Flask-Login 的基本工作流程。它管理用户的登录状态,并提供一种简便的方式让你能在视图函数中访问当前用户。
补充1
Flask-Login 中的 user_loader
是一个回调函数,其执行时机主要在处理用户的请求时。每当用户发送一个请求到 Flask 应用时,Flask-Login 都会通过在用户会话(session)中存储的用户 ID 来执行 user_loader
函数,以获取对应的用户对象。
具体步骤如下:
- 当一个请求到达 Flask 应用时,Flask-Login 会先查看用户的会话中是否有用户 ID 存在。
- 如果用户会话中存在 ID,Flask-Login 就会调用
user_loader
函数,并把这个 ID 作为参数传入。 user_loader
函数接收到这个 ID,然后查询数据库(或者其他存储用户数据的地方)找到对应的用户对象,并返回。- Flask-Login 接着将这个用户对象存储在当前请求的上下文中,这样在处理这个请求的后续步骤中,你就可以使用
current_user
变量来访问这个用户对象了。
总的来说,user_loader
的调用是在每个请求开始时进行的,目的是为了从用户的会话中恢复用户对象,这样在处理请求时就可以知道是谁发起的请求,他/她是否有权限访问特定资源等等。
补充2
假设你有一个名为 dashboard
的视图函数,它只允许已认证的用户访问。你可以使用 Flask-Login 提供的 login_required
装饰器来实现这一点:
from flask_login import login_required
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html')
如果一个已认证的用户尝试访问这个视图,他们将会正常地看到 dashboard 页面。然而,如果一个未认证的用户(例如,未登录或者会话已过期的用户)尝试访问这个视图,login_required
装饰器将会拦截这个请求,并重定向到登录页面。
你需要在 LoginManager
实例中设置登录视图的名称,以便 Flask-Login 知道未认证的用户应该被重定向到哪里:
login_manager.login_view = 'login'
这里 'login'
是登录视图函数的端点名。例如,如果你的登录视图函数像下面这样定义:
@app.route('/login', methods=['GET', 'POST'])
def login():
# authentication code here
pass
那么登录视图的端点名就是 ‘login’,所以上面的 login_manager.login_view = 'login'
表示未认证的用户将会被重定向到这个视图。
注意,你的登录视图函数应该处理两种请求方法:‘GET’ 和 ‘POST’。‘GET’ 请求用于显示登录表单,‘POST’ 请求用于提交表单。
补充3
这里我将为你展示一个简单的 Flask 登录页面的例子,这个例子使用了 Flask-Login 和 Flask-WTF(用于处理表单)。
首先,我们需要创建一个用于处理登录的表单类,如下:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Sign In')
然后,我们需要创建一个处理登录的视图函数,代码如下:
from flask import render_template, redirect, url_for, flash
from flask_login import login_user
from .forms import LoginForm
from .models import User
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None and user.check_password(form.password.data):
login_user(user)
return redirect(url_for('dashboard')) #
补充4:用户注册
用户注册通常涉及到以下几个步骤:创建一个用户注册表单,创建一个处理用户提交注册信息的视图函数,以及创建一个用于显示注册表单的模板。
首先,我们需要创建一个用户注册表单,如下:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
然后,我们需要创建一个处理用户注册的视图函数,代码如下:
from flask import render_template, redirect, url_for, flash
from .forms import RegistrationForm
from .models import User, db
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('login')) # 登录页面的路由
return render_template('register.html', form=form)
在这个视图函数中,当表单通过验证并提交时(form.validate_on_submit()
),我们创建一个新的用户对象,并设置其密码(user.set_password(form.password.data)
)。然后,我们把新用户添加到数据库会话中,并提交会话以将数据写入数据库(db.session.add(user)
和 db.session.commit()
)。最后,我们显示一个注册成功的消息,并重定向到登录页面。
最后,你需要在你的 ‘register.html’ 模板中添加一个显示注册表单的部分,代码可能如下:
<form method="POST">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=20) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=20) }}
</p>
<p>
{{ form.confirm_password.label }}<br>
{{ form.confirm_password(size=20) }}
</p>
<p>{{ form.submit() }}</p>
</form>
当用户在注册表单中输入用户名和密码并点击提交时,他们提供的数据将被发送到服务器进行处理。如果所有输入都满足验证规则(例如,两次输入的密码一致),一个新的用户就会被创建,并存储到数据库中。然后用户就会被重定向到登录页面。如果有任何验证失败,用户将被提示重新输入。
补充5:密码找回
密码找回功能的实现通常包括以下步骤:发送包含密码重置链接的电子邮件到用户的注册邮箱,用户点击链接后,被重定向到一个可以设置新密码的页面。
以下是一个简化的实现示例:
- 创建一个用于生成和验证密码重置令牌的用户模型方法:
首先,在你的用户模型中,添加一个生成令牌的方法和一个验证令牌的方法。通常,可以使用 itsdangerous
库来生成签名的令牌。
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
class User(db.Model, UserMixin):
# ...
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
return s.dumps({'user_id': self.id}).decode('utf-8')
@staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
- 创建一个发送密码重置电子邮件的函数:
接下来,你需要创建一个函数,该函数生成一个密码重置令牌,然后发送一封包含密码重置链接的电子邮件到用户的注册邮箱。这个链接应该包含令牌作为查询参数。
你可能会使用 Flask-Mail 插件来发送邮件。
- 创建密码重置请求的视图函数:
创建一个视图函数来处理密码重置请求,获取用户输入的电子邮件地址,查找关联的用户,如果找到了用户,就调用上面的函数来发送密码重置电子邮件。
- 创建处理密码重置链接的视图函数:
创建一个视图函数来处理用户点击密码重置链接后的请求。这个视图函数应该从链接的查询参数中获取令牌,然后调用用户模型的 verify_reset_token
方法来验证令牌。如果验证通过,将用户重定向到一个可以输入新密码的表单页面。
- 创建设置新密码的视图函数:
最后,你需要创建一个视图函数来处理用户提交新密码的请求。这个视图函数应该获取用户的新密码,然后更新用户模型中的密码字段,最后把更新后的用户数据存回数据库。
这个流程需要用户能够接收和发送电子邮件,并且你的应用能够发送电子邮件。这个流程在某些方面可能需要调整以适应你的具体需求,例如邮件服务的选择,以及如何处理邮件发送失败的情况。