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
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
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étodo | Descripción |
|---|---|
| is_authenticated | Si el usuario ha iniciado sesión |
| is_active | Si el usuario está activo |
| is_anonymous | Si 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
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
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
{% 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
<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