Cómo implementar la autenticación en un sistema cliente-servidor Python

PythonBeginner
Practicar Ahora

Introducción

Implementar una autenticación segura es un aspecto crucial en la construcción de sistemas cliente-servidor robustos en Python. Este tutorial te guiará a través del proceso de implementación de la autenticación en tus aplicaciones Python, comenzando con la autenticación básica de nombre de usuario y contraseña y avanzando hacia métodos basados en tokens. Al final de este laboratorio, comprenderás cómo asegurar tus aplicaciones Python con mecanismos de autenticación adecuados.

Comprensión de los Fundamentos de la Autenticación y Configuración de tu Entorno

La autenticación es el proceso de verificar la identidad de un usuario o sistema antes de otorgar acceso a recursos protegidos. En los sistemas cliente-servidor, una autenticación adecuada asegura que solo los usuarios autorizados puedan acceder a datos sensibles o realizar ciertas operaciones.

Configuración de tu Entorno

Comencemos configurando nuestro entorno de trabajo. Primero, crea un nuevo directorio para nuestro proyecto:

mkdir -p ~/project/auth_demo
cd ~/project/auth_demo

A continuación, necesitamos instalar los paquetes Python necesarios que usaremos a lo largo de este laboratorio:

pip install flask flask-login requests

Deberías ver una salida similar a esta:

Collecting flask
  Downloading Flask-2.2.3-py3-none-any.whl (101 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.8/101.8 KB 6.2 MB/s eta 0:00:00
Collecting flask-login
  Downloading Flask_Login-0.6.2-py3-none-any.whl (17 kB)
Collecting requests
  Downloading requests-2.29.0-py3-none-any.whl (62 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.5/62.5 KB 5.5 MB/s eta 0:00:00
...
Successfully installed flask-2.2.3 flask-login-0.6.2 requests-2.29.0 ...

Conceptos de Autenticación

Antes de sumergirnos en la implementación, comprendamos los conceptos básicos de autenticación:

  1. Autenticación con Nombre de Usuario y Contraseña: La forma más común donde los usuarios proporcionan credenciales para verificar su identidad.

  2. Autenticación basada en Tokens (Token-based Authentication): Después de un inicio de sesión exitoso, el servidor genera un token para que el cliente lo use en solicitudes posteriores, evitando la necesidad de enviar credenciales cada vez.

  3. Autenticación basada en Sesiones (Session-based Authentication): El servidor almacena información de la sesión después de un inicio de sesión exitoso y proporciona un ID de sesión al cliente.

Visualicemos un flujo de autenticación básico:

Cliente                                Servidor
  |                                     |
  |--- Solicitud de Inicio de Sesión (credenciales) ---->|
  |                                     |--- Verificar Credenciales
  |                                     |
  |<---- Respuesta de Autenticación -------|
  |                                     |
  |--- Solicitudes Subsiguientes (con auth) >|
  |                                     |--- Verificar Auth
  |                                     |
  |<---- Respuesta de Recurso Protegido ---|

Ahora que entendemos los conceptos básicos y hemos configurado nuestro entorno, creemos una estructura de archivos simple para nuestro proyecto:

touch ~/project/auth_demo/server.py
touch ~/project/auth_demo/client.py

En el siguiente paso, implementaremos un sistema básico de autenticación con nombre de usuario y contraseña utilizando Flask en el lado del servidor y un cliente Python para autenticarse con él.

Implementación de la Autenticación Básica con Nombre de Usuario y Contraseña

En este paso, implementaremos un sistema de autenticación básico que utiliza nombres de usuario y contraseñas. Crearemos:

  1. Un servidor Flask que maneja la autenticación
  2. Un cliente Python que se autentica con el servidor

Creación del Servidor

Implementemos nuestro servidor Flask con capacidades básicas de autenticación. Abre el archivo server.py en el editor VSCode:

code ~/project/auth_demo/server.py

Ahora, agrega el siguiente código para implementar un servidor de autenticación simple:

from flask import Flask, request, jsonify, session
import os

app = Flask(__name__)
## Generate a random secret key for session management
app.secret_key = os.urandom(24)

## Simple in-memory user database
users = {
    "alice": "password123",
    "bob": "qwerty456"
}

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists and the password is correct
    if username in users and users[username] == password:
        session['logged_in'] = True
        session['username'] = username
        return jsonify({"status": "success", "message": f"Welcome {username}!"})
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Check if the user is logged in
    if session.get('logged_in'):
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {session.get('username')}!"
        })
    else:
        return jsonify({"status": "error", "message": "Authentication required"}), 401

@app.route('/logout', methods=['POST'])
def logout():
    ## Remove session data
    session.pop('logged_in', None)
    session.pop('username', None)
    return jsonify({"status": "success", "message": "Logged out successfully"})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Este servidor:

  • Utiliza un diccionario simple en memoria para almacenar nombres de usuario y contraseñas
  • Proporciona endpoints para iniciar sesión, acceder a contenido protegido y cerrar sesión
  • Utiliza la gestión de sesiones de Flask para mantener el estado de autenticación

Creación del Cliente

Ahora, creemos un cliente que pueda autenticarse con nuestro servidor. Abre el archivo client.py:

code ~/project/auth_demo/client.py

Agrega el siguiente código:

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    print(response.json())

    ## Return the session cookies if login was successful
    return response.cookies if response.status_code == 200 else None

def access_protected(cookies=None):
    """Access a protected resource"""
    response = requests.get(f"{BASE_URL}/protected", cookies=cookies)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

def logout(cookies=None):
    """Logout from the server"""
    response = requests.post(f"{BASE_URL}/logout", cookies=cookies)

    print(f"Logout response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test with valid credentials
    print("=== Authenticating with valid credentials ===")
    cookies = login("alice", "password123")

    if cookies:
        print("\n=== Accessing protected resource ===")
        access_protected(cookies)

        print("\n=== Logging out ===")
        logout(cookies)

    ## Test with invalid credentials
    print("\n=== Authenticating with invalid credentials ===")
    login("alice", "wrongpassword")

    ## Try to access protected resource without authentication
    print("\n=== Accessing protected resource without authentication ===")
    access_protected()

Este cliente:

  • Proporciona funciones para iniciar sesión, acceder a un recurso protegido y cerrar sesión
  • Maneja las cookies para mantener la sesión entre las solicitudes
  • Prueba tanto credenciales válidas como inválidas

Ejecución del Sistema de Autenticación

Para ver nuestro sistema de autenticación en acción, necesitaremos ejecutar tanto el servidor como el cliente. Primero, iniciemos el servidor:

python ~/project/auth_demo/server.py

Deberías ver una salida similar a esta:

 * Serving Flask app 'server'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000

Ahora, abre una nueva pestaña de terminal haciendo clic en el botón "+" junto a la terminal y ejecuta el cliente:

cd ~/project/auth_demo
python client.py

Deberías ver una salida que demuestra:

  1. Inicio de sesión exitoso con credenciales válidas
  2. Acceso a recursos protegidos con autenticación válida
  3. Cierre de sesión exitoso
  4. Intento fallido de inicio de sesión con credenciales incorrectas
  5. Intento fallido de acceder a recursos protegidos sin autenticación

Esta salida confirma que nuestro sistema básico de autenticación con nombre de usuario y contraseña funciona correctamente.

En el siguiente paso, mejoraremos este sistema implementando la autenticación basada en tokens para una mayor seguridad.

Implementación de la Autenticación Basada en Tokens

En el paso anterior, creamos un sistema de autenticación básico utilizando sesiones. Si bien esto funciona para aplicaciones simples, la autenticación basada en tokens ofrece varias ventajas, particularmente para APIs y sistemas distribuidos.

En la autenticación basada en tokens:

  • El servidor genera un token después de una autenticación exitosa
  • El cliente almacena y envía este token con las solicitudes subsiguientes
  • El servidor valida el token en lugar de verificar las credenciales cada vez

Actualicemos nuestro sistema para usar JSON Web Tokens (JWT), un estándar popular para la autenticación basada en tokens.

Instalación de los Paquetes Requeridos

Primero, necesitamos instalar el paquete PyJWT:

pip install pyjwt

Deberías ver una salida que confirme la instalación:

Collecting pyjwt
  Downloading PyJWT-2.6.0-py3-none-any.whl (20 kB)
Installing collected packages: pyjwt
Successfully installed pyjwt-2.6.0

Actualización del Servidor para la Autenticación Basada en Tokens

Modifiquemos nuestro servidor para usar tokens JWT en lugar de sesiones. Actualiza el archivo server.py:

code ~/project/auth_demo/server.py

Reemplaza el código existente con:

from flask import Flask, request, jsonify
import jwt
import datetime
import os

app = Flask(__name__)
## Secret key for signing JWT tokens
SECRET_KEY = os.urandom(24)

## Simple in-memory user database
users = {
    "alice": "password123",
    "bob": "qwerty456"
}

def generate_token(username):
    """Generate a JWT token for the authenticated user"""
    ## Token expires after 30 minutes
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

    payload = {
        'username': username,
        'exp': expiration
    }

    ## Create the JWT token
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

def verify_token(token):
    """Verify the JWT token"""
    try:
        ## Decode and verify the token
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        ## Token has expired
        return None
    except jwt.InvalidTokenError:
        ## Invalid token
        return None

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists and the password is correct
    if username in users and users[username] == password:
        ## Generate a JWT token
        token = generate_token(username)

        return jsonify({
            "status": "success",
            "message": f"Welcome {username}!",
            "token": token
        })
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if payload:
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {payload['username']}!"
        })
    else:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Este servidor actualizado:

  • Genera tokens JWT tras una autenticación exitosa
  • Valida los tokens para recursos protegidos
  • Tiene endpoints para iniciar sesión y acceder a contenido protegido

Actualización del Cliente para la Autenticación Basada en Tokens

Ahora, actualicemos nuestro cliente para usar los tokens JWT. Actualiza el archivo client.py:

code ~/project/auth_demo/client.py

Reemplaza el código existente con:

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    data = response.json()
    print(data)

    ## Return the token if login was successful
    return data.get('token') if response.status_code == 200 else None

def access_protected(token=None):
    """Access a protected resource using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/protected", headers=headers)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test with valid credentials
    print("=== Authenticating with valid credentials ===")
    token = login("alice", "password123")

    if token:
        print("\n=== Accessing protected resource with valid token ===")
        access_protected(token)

    ## Test with invalid credentials
    print("\n=== Authenticating with invalid credentials ===")
    login("alice", "wrongpassword")

    ## Try to access protected resource without token
    print("\n=== Accessing protected resource without token ===")
    access_protected()

    ## Try to access protected resource with invalid token
    print("\n=== Accessing protected resource with invalid token ===")
    access_protected("invalid.token.value")

Este cliente actualizado:

  • Recupera y almacena el token JWT después de una autenticación exitosa
  • Envía el token en el encabezado Authorization para acceder a recursos protegidos
  • Prueba varios escenarios, incluyendo intentos de autenticación válidos e inválidos

Ejecución del Sistema de Autenticación Basado en Tokens

Ahora, ejecutemos nuestro sistema de autenticación basado en tokens actualizado. Primero, detén cualquier proceso de servidor en ejecución con Ctrl+C, luego inicia el servidor actualizado:

python ~/project/auth_demo/server.py

Abre una nueva pestaña de terminal o usa la segunda pestaña existente para ejecutar el cliente:

cd ~/project/auth_demo
python client.py

Deberías ver una salida que demuestra:

  1. Inicio de sesión exitoso con credenciales válidas, recibiendo un token JWT
  2. Acceso a recursos protegidos con un token válido
  3. Intento fallido de inicio de sesión con credenciales incorrectas
  4. Intento fallido de acceder a recursos protegidos sin un token
  5. Intento fallido de acceder a recursos protegidos con un token inválido

Esta salida confirma que nuestro sistema de autenticación basado en tokens funciona correctamente.

Ventajas de la Autenticación Basada en Tokens

La autenticación basada en tokens ofrece varias ventajas sobre la autenticación basada en sesiones:

  1. Sin estado (Stateless): El servidor no necesita almacenar información de la sesión.
  2. Escalabilidad: Funciona bien en entornos distribuidos con múltiples servidores.
  3. Amigable para dispositivos móviles (Mobile-friendly): Adecuado para aplicaciones móviles donde las cookies podrían no funcionar bien.
  4. Entre dominios (Cross-domain): Se puede usar fácilmente en diferentes dominios.
  5. Seguridad: Los tokens se pueden configurar para que expiren, reduciendo el riesgo de secuestro de sesión.

En aplicaciones del mundo real, es posible que desees mejorar aún más tu sistema de autenticación con características como la actualización de tokens, el control de acceso basado en roles y el almacenamiento seguro de tokens.

Mejora de la Seguridad con el Hashing de Contraseñas

En nuestras implementaciones anteriores, almacenamos las contraseñas en texto plano, lo cual es un riesgo de seguridad significativo. En este paso, mejoraremos nuestro sistema de autenticación implementando el hashing de contraseñas utilizando el algoritmo bcrypt.

¿Por qué hacer Hashing de Contraseñas?

Almacenar contraseñas en texto plano presenta varios riesgos de seguridad:

  • Si tu base de datos se ve comprometida, los atacantes pueden usar inmediatamente las contraseñas
  • Los usuarios a menudo reutilizan contraseñas en múltiples sitios, por lo que una brecha puede afectar a otros servicios
  • Viola las mejores prácticas de seguridad y puede infringir las regulaciones de protección de datos

El hashing de contraseñas resuelve estos problemas al:

  • Convertir las contraseñas en cadenas de longitud fija que no se pueden revertir
  • Agregar una "sal" para evitar que los atacantes usen tablas de búsqueda precalculadas (tablas arcoíris)
  • Asegurar que incluso si dos usuarios tienen la misma contraseña, sus hashes serán diferentes

Instalación de los Paquetes Requeridos

Primero, necesitamos instalar el paquete bcrypt:

pip install bcrypt

Deberías ver una salida que confirme la instalación:

Collecting bcrypt
  Downloading bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl (593 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 593.8/593.8 KB 10.5 MB/s eta 0:00:00
Installing collected packages: bcrypt
Successfully installed bcrypt-4.0.1

Creación de un Sistema de Registro de Usuarios

Mejoremos nuestro servidor para incluir el registro de usuarios con hashing de contraseñas. Actualiza el archivo server.py:

code ~/project/auth_demo/server.py

Reemplaza el código existente con:

from flask import Flask, request, jsonify
import jwt
import datetime
import os
import bcrypt

app = Flask(__name__)
## Secret key for signing JWT tokens
SECRET_KEY = os.urandom(24)

## Store users as a dictionary with username as key and a dict of hashed password and roles as value
users = {}

def hash_password(password):
    """Hash a password using bcrypt"""
    ## Convert password to bytes if it's a string
    if isinstance(password, str):
        password = password.encode('utf-8')

    ## Generate a salt and hash the password
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password, salt)

    return hashed

def check_password(password, hashed):
    """Verify a password against its hash"""
    ## Convert password to bytes if it's a string
    if isinstance(password, str):
        password = password.encode('utf-8')

    ## Check if the password matches the hash
    return bcrypt.checkpw(password, hashed)

def generate_token(username, role):
    """Generate a JWT token for the authenticated user"""
    ## Token expires after 30 minutes
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

    payload = {
        'username': username,
        'role': role,
        'exp': expiration
    }

    ## Create the JWT token
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

def verify_token(token):
    """Verify the JWT token"""
    try:
        ## Decode and verify the token
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        ## Token has expired
        return None
    except jwt.InvalidTokenError:
        ## Invalid token
        return None

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()

    ## Extract registration data
    username = data.get('username')
    password = data.get('password')

    ## Basic validation
    if not username or not password:
        return jsonify({"status": "error", "message": "Username and password are required"}), 400

    ## Check if the username already exists
    if username in users:
        return jsonify({"status": "error", "message": "Username already exists"}), 409

    ## Hash the password and store the user
    hashed_password = hash_password(password)
    users[username] = {
        'password': hashed_password,
        'role': 'user'  ## Default role
    }

    return jsonify({
        "status": "success",
        "message": f"User {username} registered successfully"
    })

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists
    if username not in users:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

    ## Check if the password is correct
    if check_password(password, users[username]['password']):
        ## Generate a JWT token
        token = generate_token(username, users[username]['role'])

        return jsonify({
            "status": "success",
            "message": f"Welcome {username}!",
            "token": token
        })
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if payload:
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {payload['username']}!",
            "role": payload['role']
        })
    else:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

@app.route('/admin', methods=['GET'])
def admin():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if not payload:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

    ## Check if user has admin role
    if payload['role'] != 'admin':
        return jsonify({"status": "error", "message": "Admin access required"}), 403

    return jsonify({
        "status": "success",
        "message": f"Welcome to the admin panel, {payload['username']}!"
    })

## Add an admin user for testing
admin_password = hash_password("admin123")
users["admin"] = {
    'password': admin_password,
    'role': 'admin'
}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Este servidor mejorado:

  • Utiliza bcrypt para el hashing seguro de contraseñas
  • Incluye un endpoint de registro de usuarios
  • Agrega control de acceso basado en roles (administrador vs. usuarios regulares)
  • Pre-crea un usuario administrador para pruebas

Actualización del Cliente

Ahora, actualicemos nuestro cliente para probar estas nuevas características. Actualiza el archivo client.py:

code ~/project/auth_demo/client.py

Reemplaza el código existente con:

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def register(username, password):
    """Register a new user"""
    response = requests.post(
        f"{BASE_URL}/register",
        json={"username": username, "password": password}
    )

    print(f"Registration response: {response.status_code}")
    print(response.json())

    return response.status_code == 200

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    data = response.json()
    print(data)

    ## Return the token if login was successful
    return data.get('token') if response.status_code == 200 else None

def access_protected(token=None):
    """Access a protected resource using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/protected", headers=headers)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

def access_admin(token=None):
    """Access the admin panel using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/admin", headers=headers)

    print(f"Admin panel response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test user registration
    print("=== Registering a new user ===")
    register("testuser", "testpass123")

    ## Try to register with the same username (should fail)
    print("\n=== Registering with existing username ===")
    register("testuser", "differentpass")

    ## Test regular user login and access
    print("\n=== Regular user login ===")
    user_token = login("testuser", "testpass123")

    if user_token:
        print("\n=== Regular user accessing protected resource ===")
        access_protected(user_token)

        print("\n=== Regular user trying to access admin panel ===")
        access_admin(user_token)

    ## Test admin login and access
    print("\n=== Admin login ===")
    admin_token = login("admin", "admin123")

    if admin_token:
        print("\n=== Admin accessing protected resource ===")
        access_protected(admin_token)

        print("\n=== Admin accessing admin panel ===")
        access_admin(admin_token)

Este cliente actualizado:

  • Prueba la funcionalidad de registro de usuarios
  • Demuestra el control de acceso basado en roles
  • Prueba tanto la autenticación de usuarios regulares como la de administradores

Ejecución del Sistema de Autenticación Mejorado

Ahora, ejecutemos nuestro sistema de autenticación mejorado. Primero, detén cualquier proceso de servidor en ejecución con Ctrl+C, luego inicia el servidor actualizado:

python ~/project/auth_demo/server.py

Abre una nueva pestaña de terminal o usa la segunda pestaña existente para ejecutar el cliente:

cd ~/project/auth_demo
python client.py

Deberías ver una salida que demuestra:

  1. Registro exitoso de usuario
  2. Registro fallido con un nombre de usuario existente
  3. Inicio de sesión de usuario regular y acceso a recursos protegidos
  4. Intento fallido de un usuario regular de acceder al panel de administración
  5. Inicio de sesión de administrador y acceso tanto a recursos protegidos como al panel de administración

Esta salida confirma que nuestro sistema de autenticación mejorado con hashing de contraseñas y control de acceso basado en roles funciona correctamente.

Ventajas de Seguridad

Nuestro sistema mejorado ahora proporciona:

  1. Almacenamiento Seguro de Contraseñas: Las contraseñas se hash utilizando bcrypt, una función de hashing lenta diseñada para resistir ataques de fuerza bruta.
  2. Control de Acceso Basado en Roles: Diferentes usuarios pueden tener diferentes niveles de acceso según sus roles.
  3. Autenticación Basada en Tokens: Continúa proporcionando los beneficios de los tokens JWT que implementamos anteriormente.
  4. Registro de Usuarios: Permite la creación dinámica de usuarios con almacenamiento seguro de contraseñas.

Estas mejoras hacen que nuestro sistema de autenticación sea mucho más robusto y adecuado para aplicaciones del mundo real.

Resumen

En este laboratorio, has aprendido a implementar la autenticación en un sistema cliente-servidor de Python, desde conceptos básicos hasta técnicas avanzadas. Has logrado:

  1. Comprender los conceptos fundamentales de la autenticación en sistemas cliente-servidor
  2. Implementar la autenticación básica de nombre de usuario y contraseña utilizando sesiones de Flask
  3. Mejorar la seguridad con la autenticación basada en tokens utilizando JWT
  4. Agregar hashing seguro de contraseñas con bcrypt
  5. Implementar el control de acceso basado en roles para diferentes tipos de usuarios

Estas habilidades proporcionan una base sólida para construir aplicaciones Python seguras. Recuerda que la autenticación es solo un aspecto de la seguridad de la aplicación; en sistemas de producción, también debes considerar:

  • Usar HTTPS para todas las comunicaciones
  • Implementar la limitación de velocidad (rate limiting) para prevenir ataques de fuerza bruta
  • Agregar registro y monitoreo para actividades sospechosas
  • Mantener todas las dependencias actualizadas para abordar las vulnerabilidades de seguridad

Al aplicar estos principios, puedes construir aplicaciones Python que protejan eficazmente los datos de los usuarios y mantengan operaciones seguras.