下载工作台
Flask Web 开发

Flask-Login 认证与会话

试读上半部分 · 解锁后可读全文

第 6 章 · Flask-Login 认证与会话

本章目标:使用 Flask-Login 管理登录态;定义继承 UserMixinUser 模型;配置 LoginManageruser_loader;用 @login_required 保护 admin 商品管理路由;实现注册、登录、登出完整流程;理解 Session Cookie 与 remember 选项;与 Django LoginView 对照学习。

学时建议:4~5 小时(含 1.5 小时跟练)

前置:完成 flask-web ch05Product/Category 已入库;User 本章新建)。


6.1 场景说明:谁能改商品?

api-demo 的 /admin/products/ 不能对匿名开放。与 shop-demo 的角色表类似,本章先实现单角色运营账号is_staff),细粒度权限留作扩展。

路径匿名已登录
/auth/login可访问跳转仪表盘
/auth/register可访问(练习环境)可关闭
/admin/products/*跳转登录可 CRUD
/api/v1/products可读(本章)ch07 加 Token

管理页:http://127.0.0.1:5000/admin/;对外 API 文档域名仍为虚构 https://api.example.com


6.2 安装 Flask-Login

pip install "flask-login>=0.6,<1"

api_demo/extensions.py

from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect

db = SQLAlchemy()
migrate = Migrate()
csrf = CSRFProtect()
login_manager = LoginManager()

api_demo/__init__.py

from api_demo.extensions import csrf, db, login_manager, migrate

def create_app(config_name=None):
    # ...
    login_manager.init_app(app)
    login_manager.login_view = "auth.login"
    login_manager.login_message = "请先登录"
    login_manager.login_message_category = "info"
    # ...
配置含义
login_view未登录时重定向的端点
login_messageflash 提示文案

6.3 User 模型与 UserMixin

api_demo/models/user.py

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

from api_demo.extensions import db


class User(UserMixin, db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, nullable=False, index=True)
    email = db.Column(db.String(120), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(256), nullable=False)
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    is_staff = db.Column(db.Boolean, default=False, nullable=False)

    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)

    def __repr__(self):
        return f"<User {self.username}>"

更新 api_demo/models/__init__.py

from api_demo.models.category import Category
from api_demo.models.product import Product
from api_demo.models.user import User

__all__ = ["Category", "Product", "User"]

UserMixin 提供默认属性:is_authenticatedis_activeis_anonymousget_id()

迁移:

flask db migrate -m "add user"
flask db upgrade

6.4 LoginManager 与 user_loader

api_demo/__init__.pyapi_demo/auth.py

from api_demo.extensions import db, login_manager
from api_demo.models import User


@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))   # SQLAlchemy 2.x 推荐写法,替代旧的 Query.get

每个请求若 session 中有用户 id,load_user 会恢复 current_user

from flask_login import current_user

# current_user.is_authenticated
# current_user.email

6.5 认证表单

api_demo/forms/auth.py

from flask_wtf import FlaskForm
from wtforms import BooleanField, PasswordField, StringField
from wtforms.validators import DataRequired, Email, EqualTo, Length


class LoginForm(FlaskForm):
    email = StringField("邮箱", validators=[DataRequired(), Email()])
    password = PasswordField("密码", validators=[DataRequired()])
    remember = BooleanField("记住我")


class RegisterForm(FlaskForm):
    email = StringField("邮箱", validators=[DataRequired(), Email()])
    password = PasswordField(
        "密码",
        validators=[DataRequired(), Length(min=8, message="至少 8 位")],
    )
    password2 = PasswordField(
        "确认密码",
        validators=[DataRequired(), EqualTo("password", message="两次密码不一致")],
    )

6.6 auth 蓝图:登录、登出与注册

api_demo/routes/auth.py

from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import current_user, login_required, login_user, logout_user

from api_demo.extensions import db
from api_demo.forms.auth import LoginForm, RegisterForm
from api_demo.models import User

bp = Blueprint("auth", __name__, url_prefix="/auth")


@bp.route("/login", methods=["GET", "POST"])
def login():
    if current_user.is_authenticated:

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。