Flask-Login — Регистрация пользователя, вход, выход

В этой главе вы научитесь реализовывать аутентификацию пользователя в сессии с помощью Flask-Login — важнейшей меры безопасности в веб-разработке на Flask.


Что такое Flask-Login?

HTTP-запросы в веб-приложениях по своей природе не сохраняют состояние — сервер не запоминает автоматически, кто вы.

Flask-LoginчерезSessionмеханизм поддерживает состояние пользователя: после входа сервер сохраняет ID пользователя в Session, а последующие запросы автоматически определяют текущего пользователя.


Установка и инициализация

(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'   # Ключ шифрования сессии (в продакшене используйте переменную окружения)

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 — шифрование сессии, CSRF-токены и 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 использует эту функцию для восстановления объекта пользователя из сессии
@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')

        # Проверить, существуют ли уже username и email
        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')

        # После входа перенаправьте на страницу, посещённую до входа (параметр next в URL)
        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'))

Референсы, необходимые в дизайне приложения

Пример

# Путь к файлу: зарегистрируйте auth Blueprint в app.py
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)

Никогда не храните пароли в открытом виде!generate_password_hash()Используйте алгоритм PBKDF2 для хеширования паролей с солью. Даже если база данных будет скомпрометирована, злоумышленники не смогут восстановить пароль. При проверке пароля используйтеcheck_password_hash()。


Создайте шаблоны для входа и регистрации

Пример

<!-- Путь к файлу: app/templates/login.html -->
{% extends 'base.html' %}

{% block title %}Вход - EXAMPLE Blog{% 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>
    </форма>
    <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.


Итоги главы

В этой главе вы интегрировали аутентификацию через Session в Flask-Login: UserMixin + user_loader связали с моделью пользователя, login_user/logout_user управляли Session, werkzeug хешировал пароли, @login_required защищал представления, а current_user в шаблонах проверял статус.

Теперь в блоге есть полный цикл регистрации, входа и выхода пользователя.

Другие расширения