现在位置: 首页 > Flask 教程 > 正文

Flask-Login — 用户注册、登录、登出

本章你将学会用 Flask-Login 实现 Session 用户认证,这是 Flask Web 开发最重要的安全保障。


Flask-Login 是什么?

Web 应用的 HTTP 请求本质上是无状态的——服务器不会自动记住你是谁。

Flask-Login 通过 Session 机制实现用户状态的维护:登录后服务器在 Session 中存入用户 ID,后续请求自动识别当前用户。


安装与初始化

(venv) $ pip install flask-login

实例

# 文件路径:app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY'] = 'your-secret-key-here'   # Session 加密密钥(生产环境用环境变量)

db = SQLAlchemy(app)

# 初始化 LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'     # 未登录用户重定向到登录页
login_manager.login_message = '请先登录后再访问。'

SECRET_KEY 是 Flask 安全体系的基石——Session 加密、CSRF Token、Flash 消息都依赖它。生产环境中必须从环境变量读取,不可硬编码。


定义 User 模型

Flask-Login 要求 User 模型实现 UserMixin 提供的几个方法。

实例

# 文件路径:models.py 新增
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
    """用户模型"""
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), 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}>'

# 定义 user_loader:Flask-Login 用此函数从 session 中恢复用户对象
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

UserMixin 自动为 User 模型注入以下属性和方法:

属性/方法说明
is_authenticated用户是否已登录
is_active用户是否处于活跃状态
is_anonymous是否为匿名用户(未登录)
get_id()获取用户主键 ID

生成迁移并执行:

(venv) $ flask db migrate -m "新增 User 模型"
(venv) $ flask db upgrade

创建认证 Blueprint

实例

# 文件路径:app/blueprints/auth.py
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user
from app.models import User, db

auth_bp = Blueprint('auth', __name__)

@auth_bp.route("/register", methods=['GET', 'POST'])
def register():
    """用户注册"""
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        email = request.form.get('email', '').strip()
        password = request.form.get('password', '')

        # 校验必填字段
        if not username or not email or not password:
            flash('所有字段都必须填写。', 'error')
            return render_template('register.html')

        # 检查用户名和邮箱是否已存在
        if User.query.filter_by(username=username).first():
            flash('用户名已被使用。', 'error')
            return render_template('register.html')
        if User.query.filter_by(email=email).first():
            flash('邮箱已被注册。', 'error')
            return render_template('register.html')

        # 创建用户
        user = User(username=username, email=email)
        user.set_password(password)    # 加密存储密码
        db.session.add(user)
        db.session.commit()

        login_user(user)               # 注册后自动登录
        flash(f'注册成功,欢迎你,{username}!', 'success')
        return redirect(url_for('main.index'))

    return render_template('register.html')


@auth_bp.route("/login", methods=['GET', 'POST'])
def login():
    """用户登录"""
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        password = request.form.get('password', '')

        # user_loader 从数据库查找用户
        user = User.query.filter_by(username=username).first()

        # 验证密码(密码本身不会被记录到日志)
        if user is None or not user.check_password(password):
            flash('用户名或密码错误。', 'error')
            return render_template('login.html')

        login_user(user, remember=request.form.get('remember'))
        flash(f'欢迎回来,{username}!', 'success')

        # 登录后重定向到登录前访问的页面(URL 中 next 参数)
        next_page = request.args.get('next')
        return redirect(next_page or url_for('main.index'))

    return render_template('login.html')


@auth_bp.route("/logout")
@login_required       # 只有登录用户才能执行登出
def logout():
    logout_user()
    flash('已安全登出。', 'info')
    return redirect(url_for('main.index'))

在设计 App 中需要的引用

实例

# 文件路径:app.py 中注册 auth Blueprint
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)

永远不要明文存储密码!generate_password_hash() 使用 PBKDF2 算法对密码做 salted hash。即使数据库泄露,攻击者也无法反向推导密码。校验密码时用 check_password_hash()


创建登录和注册模板

实例

<!-- 文件路径:app/templates/login.html -->
{% extends 'base.html' %}

{% block title %}登录 - RUNOOB 博客{% endblock %}

{% block content %}
<div class="auth-form">
    <h2>登录</h2>
    <form method="post">
        <div class="form-group">
            <label>用户名</label>
            <input type="text" name="username" required>
        </div>
        <div class="form-group">
            <label>密码</label>
            <input type="password" name="password" required>
        </div>
        <div class="form-group">
            <label>
                <input type="checkbox" name="remember"> 记住我
            </label>
        </div>
        <button type="submit" class="btn-submit">登录</button>
    </form>
    <p class="form-footer">
        还没有账号?<a href="{{ url_for('auth.register') }}">立即注册</a>
    </p>
</div>
{% endblock %}

更新导航栏显示登录状态

实例

<!-- 文件路径:app/templates/base.html 导航栏部分 -->
<nav>
    <a href="/">首页</a>
    {% if current_user.is_authenticated %}
        <span class="user-name">{{ current_user.username }}</span>
        <a href="{{ url_for('auth.logout') }}">登出</a>
    {% else %}
        <a href="{{ url_for('auth.login') }}">登录</a>
        <a href="{{ url_for('auth.register') }}">注册</a>
    {% endif %}
</nav>

current_user 是 Flask-Login 自动注入的代理对象,在所有模板和视图函数中都可以直接使用。登录状态下它是一个 User 对象,未登录时是 AnonymousUserMixin 实例。


本章小结

本章你接入了 Flask-Login Session 认证:UserMixin + user_loader 组合用户模型、login_user/logout_user 管理 Session、werkzeug 哈希密码、@login_required 保护视图、模板中 current_user 判断状态。

现在博客有了完整的用户注册、登录、登出流程。