Flask-Login — Registro, inicio de sesión y cierre de sesión de usuarios

En este capítulo aprenderás a implementar la autenticación de usuarios por sesión con Flask-Login, que es la garantía de seguridad más importante en el desarrollo web con Flask.


¿Qué es Flask-Login?

Las solicitudes HTTP de las aplicaciones web son inherentemente sin estado: el servidor no recuerda automáticamente quién eres.

Flask-LoginA través delSessionmecanismo de sesión se mantiene el estado del usuario: después del inicio de sesión, el servidor guarda el ID del usuario en la sesión, y las solicitudes posteriores identifican automáticamente al usuario actual.


Instalación e inicialización

(venv) $ pip install flask-login

Ejemplo

# Ruta de archivo: 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'   # Clave de cifrado de sesión (en producción se usa variable de entorno)

db = SQLAlchemy(app)

# Inicializar LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'     # Redirigir a los usuarios no autenticados a la página de inicio de sesión
login_manager.login_message = 'Por favor, inicia sesión antes de acceder.'

SECRET_KEYEs la piedra angular del sistema de seguridad de Flask: el cifrado de sesión, el token CSRF y los mensajes Flash dependen de él. En el entorno de producción debe leerse desde una variable de entorno, nunca codificarse.


Definir el modelo User

Flask-Login requiere que el modelo User implementeUserMixinvarios métodos proporcionados.

Ejemplo

# Ruta de archivo: models.py (nuevo)
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
    """Modelo de usuario"""
    __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):
        """Cifra la contraseña con hash (nunca almacenes contraseñas en texto plano)"""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Verifica si la contraseña coincide"""
        return check_password_hash(self.password_hash, password)

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

# Definir user_loader: Flask-Login usa esta función para restaurar el objeto de usuario desde la sesión
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

UserMixin inyecta automáticamente las siguientes propiedades y métodos al modelo User:

Propiedad/MétodoDescripción
is_authenticatedSi el usuario ha iniciado sesión
is_activeSi el usuario está activo
is_anonymousSi es un usuario anónimo (sin iniciar sesión)
get_id()Obtener el ID de clave principal del usuario

Generar y ejecutar la migración:

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

Crear un Blueprint de autenticación

Ejemplo

# Ruta de archivo: 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():
    """Registro de usuario"""
    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', '')

        # Validar campos obligatorios
        if not username or not email or not password:
            flash('Todos los campos son obligatorios.', 'error')
            return render_template('register.html')

        # Comprobar si el nombre de usuario y el correo ya existen
        if User.query.filter_by(username=username).first():
            flash('El nombre de usuario ya está en uso.', 'error')
            return render_template('register.html')
        if User.query.filter_by(email=email).first():
            flash('El correo ya está registrado.', 'error')
            return render_template('register.html')

        # Crear usuario
        user = User(username=username, email=email)
        user.set_password(password)    # Almacenar la contraseña cifrada
        db.session.add(user)
        db.session.commit()

        login_user(user)               # Iniciar sesión automáticamente después del registro
        flash(f'¡Registro exitoso, bienvenido, {username}!', 'success')
        return redirect(url_for('main.index'))

    return render_template('register.html')


@auth_bp.route("/login", methods=['GET', 'POST'])
def login():
    """Inicio de sesión de usuario"""
    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 busca al usuario en la base de datos
        user = User.query.filter_by(username=username).first()

        # Verificar la contraseña (la contraseña en sí no se registra en los logs)
        if user is None or not user.check_password(password):
            flash('Nombre de usuario o contraseña incorrectos.', 'error')
            return render_template('login.html')

        login_user(user, remember=request.form.get('remember'))
        flash(f'¡Bienvenido de nuevo, {username}!', 'success')

        # Después de iniciar sesión, redirigir a la página visitada antes del inicio de sesión (parámetro next en la 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       # Solo los usuarios autenticados pueden cerrar sesión
def logout():
    logout_user()
    flash('Has cerrado sesión de forma segura.', 'info')
    return redirect(url_for('main.index'))

Referencias necesarias en el diseño de la App

Ejemplo

# Ruta de archivo: registrar el Blueprint auth en app.py
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)

¡Nunca almacenes contraseñas en texto plano!generate_password_hash()Usa el algoritmo PBKDF2 para aplicar un hash con sal a la contraseña. Incluso si la base de datos se filtra, los atacantes no pueden revertir la contraseña. Al verificar la contraseña, usacheck_password_hash()。


Crear las plantillas de inicio de sesión y registro

Ejemplo

<!-- Ruta de archivo: app/templates/login.html -->
{% extends 'base.html' %}

{% block title %}Iniciar sesión - Blog EJEMPLO{% endblock %}

{% block content %}
<div class="auth-form">
    <h2>Iniciar sesión</h2>
    <form method="post">
        <div class="form-group">
            <label>Nombre de usuario</label>
            <input type="text" name="username" required>
        </div>
        <div class="form-group">
            <label>Contraseña</label>
            <input type="password" name="password" required>
        </div>
        <div class="form-group">
            <label>
                <input type="checkbox" name="remember">Recuérdame
            </label>
        </div>
        <button type="submit" class="btn-submit">Iniciar sesión</button>
    </form>
    <p class="form-footer">
¿Aún no tienes cuenta?<a href="{{ url_for('auth.register') }}">Regístrate ahora</a>
    </p>
</div>
{% endblock %}

Actualizar la barra de navegación para mostrar el estado de inicio de sesión

Ejemplo

<!-- Ruta de archivo: parte de la barra de navegación de app/templates/base.html -->
<nav>
    <a href="/">Inicio</a>
    {% if current_user.is_authenticated %}
        <span class="user-name">{{ current_user.username }}</span>
        <a href="{{ url_for('auth.logout') }}">Cerrar sesión</a>
    {% else %}
        <a href="{{ url_for('auth.login') }}">Iniciar sesión</a>
        <a href="{{ url_for('auth.register') }}">Registrarse</a>
    {% endif %}
</nav>

current_userEs un objeto proxy inyectado automáticamente por Flask-Login, que se puede usar directamente en todas las plantillas y funciones de vista. Cuando se ha iniciado sesión es un objeto User; cuando no, es una instancia de AnonymousUserMixin.


Resumen del capítulo

En este capítulo has integrado la autenticación de sesión de Flask-Login: UserMixin + user_loader para combinar el modelo de usuario, login_user/logout_user para gestionar la sesión, werkzeug para el hash de contraseñas, @login_required para proteger vistas, y current_user en las plantillas para comprobar el estado.

Ahora el blog tiene un flujo completo de registro, inicio de sesión y cierre de sesión de usuarios.

Otras extensiones