Flask-Login — Регистрация пользователя, вход, выход
В этой главе вы научитесь реализовывать аутентификацию пользователя в сессии с помощью Flask-Login — важнейшей меры безопасности в веб-разработке на Flask.
Что такое Flask-Login?
HTTP-запросы в веб-приложениях по своей природе не сохраняют состояние — сервер не запоминает автоматически, кто вы.
Flask-LoginчерезSessionмеханизм поддерживает состояние пользователя: после входа сервер сохраняет ID пользователя в Session, а последующие запросы автоматически определяют текущего пользователя.
Установка и инициализация
(venv) $ pip install flask-login
Пример
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несколько предоставляемых методов.
Пример
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 аутентификации
Пример
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'))
Референсы, необходимые в дизайне приложения
Пример
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)
Никогда не храните пароли в открытом виде!
generate_password_hash()Используйте алгоритм PBKDF2 для хеширования паролей с солью. Даже если база данных будет скомпрометирована, злоумышленники не смогут восстановить пароль. При проверке пароля используйтеcheck_password_hash()。
Создайте шаблоны для входа и регистрации
Пример
{% 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 %}
Обновите панель навигации, чтобы отображался статус входа
Пример
<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 в шаблонах проверял статус.
Теперь в блоге есть полный цикл регистрации, входа и выхода пользователя.
Другие расширения