Aprendamos a proteger una API REST con tokens web JSON para evitar que los usuarios y las aplicaciones de terceros abusen de ella.
Construiremos un servicio de base de datos utilizando SQLite y permitiremos a los usuarios acceder a él a través de una API REST utilizando métodos HTTP como POST y PUT
Además, conoceremos por qué los tokens web JSON son una forma adecuada de proteger la API de reposo en lugar de la autenticación digest y básica. Antes de continuar, entendemos el término tokens web JSON, API REST y framework Flask
Tokens web JSON
El token web JSON, también conocido como JWT, es la forma segura de transferir tokens aleatorios entre dos partes o entidades. JSON se compone normalmente de tres partes como las siguientes
- Carga útil
- Cabecera
- Firma
JSON utiliza dos tipos de formas de estructura cuando transfiere datos o información entre dos partes
- Serializada
- Deserializado
La forma serializada se utiliza cuando se transfieren datos a la red a través de cada solicitud y respuesta, mientras que la forma deserializada se utiliza cuando se leen y escriben datos en el token web
En la forma serializada, hay tres componentes
- Cabecera
- Carga útil
- Firma
El componente de cabecera define la información criptográfica sobre las fichas. Por ejemplo
- ¿Es un JWT ¿firmado o sin firmar?
- Definir técnicas de algoritmo
La forma deserializada, a diferencia de la forma serializada, contiene dos componentes
- Carga útil
- Cabecera
API REST
API (interfaz de programación de aplicaciones) permite la comunicación entre dos aplicaciones para recuperar o enviar los datos. Existen dos tipos populares de API: la API web y la API de sistema
En este artículo, sólo nos ocuparemos de la API web. Existen dos tipos de API web
- API de solicitud - respuesta: Rest, GraphQL, Llamada a procedimiento remoto (RPC)
- API dirigida por eventos: WebHooks, Web Sockets, HTTP Streaming
La API REST pertenece a la categoría solicitud-respuesta. Hace uso de métodos HTTP como GET, POST y PUT para realizar las operaciones de la API
Un ejemplo clásico es cuando un usuario envía un método GET al servicio web para solicitar o recuperar un recurso específico o una colección de recursos. A continuación, el servidor devuelve el recurso específico o la colección de recursos al usuario que lo solicitó
Marco Flask
Flask es un marco de trabajo basado en python. Es un micro-marco utilizado por los desarrolladores de python para construir API de descanso. Se llama micro-marco porque permite a los desarrolladores, por ejemplo, añadir autenticación personalizada y cualquier otro sistema backend basado en preferencias
Empecemos con la implementación. La configuración de mi sistema es la siguiente
- Ubuntu como SO
- Python 2.7
- Cartero
Configurar un entorno virtual utilizando virtualenv
Necesitamos configurar un entorno virtual para asegurarnos de que algunos paquetes no entrarán en conflicto con los paquetes del sistema. Utilicemos virtualenv
para configurar un nuevo entorno virtual
Suponiendo que tiene el comando pip
disponible en su sistema, ejecute el siguiente comando a través de pip
para instalar
pip install virtualenv
Si no tiene pip en su máquina, entonces siga esta documentación para instalar pip en su sistema
A continuación, vamos a crear un directorio para almacenar o guardar nuestro entorno virtual. Utilice el comando mkdir
que se muestra a continuación para crear un directorio
mkdir flaskproject
Cambie al directorio flaskproject
utilizando el siguiente comando
cd
flaskproject
Dentro del directorio flaskproject
, utilice la herramienta virtualenv
para crear un entorno virtual como se muestra a continuación
virtualenv
flaskapi
Después de haber utilizado la herramienta virtualenv
para crear el entorno virtual, ejecute el comando cd
para cambiar al directorio flaskapi
como entorno virtual y actívelo utilizando el siguiente comando
source bin/activar
Ejecute todas las tareas relacionadas con este proyecto dentro del entorno virtual
Instale los paquetes utilizando pip
Ahora es el momento de instalar paquetes como el framework flask y PyJWT que utilizaremos para construir la API restante y otros paquetes necesarios para nuestro proyecto API
Cree un archivo requirements.txt
con los siguientes paquetes
Flask
datetime
uuid
Flask-SQLAlchemy
PyJWT
Instálelos con pip
pip install -r
requisitos.txt
Configurar una base de datos
Vamos a instalar SQLite
apt-get install sqlite3
Cree una base de datos llamada biblioteca. Dentro de esta base de datos, crearemos dos tablas, la tabla Usuarios
y la tabla Autores
La tabla Usuarios contendrá los usuarios registrados. Sólo los usuarios registrados podrán tener acceso a la tabla Autores
La tabla Autores almacenará la información de los autores o detalles como el nombre del autor, el país de nacimiento, etc. enviados por los usuarios registrados
Cree la base de datos utilizando el siguiente comando
sqlite3 library.db
Puede comprobar si se ha creado correctamente la base de datos utilizando el siguiente comando
.databases
Abra un nuevo terminal y ejecute lo siguiente en el entorno virtual que hemos creado anteriormente
toque app.py
Pegue el siguiente código dentro del archivo llamado app.
py
from flask import Flask, request, jsonify, make_response
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import uuid
import jwt
import datetime
from functools import wraps
La primera línea del código anterior importa paquetes como request
y jsonify
. Haremos uso de request
para realizar un seguimiento de los datos a nivel de petición durante una petición y utilizaremos jsonify
para dar salida a las respuestas en formato JSON
En la siguiente línea, importamos SQLAlchemy de flask_sqlalchemy
para integrar las características de SQLAlchemy en flask
De werkzeug.
seguridad, importamos generate_password_hash
para generar hash de contraseñas para los usuarios y check_password_hash
para comprobar la contraseña del usuario al comparar la contraseña enviada por los usuarios con las contraseñas de los usuarios almacenadas en la base de datos
Por último, importamos uuid
también conocidos como identificadores únicos universales para generar números de identificación aleatorios para los usuarios
Aún así, dentro del archivo app
.py, implemente los ajustes de configuración para la API de la librería utilizando el siguiente código dentro del archivo app.py
Coloque el siguiente código debajo de la sentencia import
app = Flask(__name__)
app.config['SECRET_KEY']='Th1s1ss3cr3t'
app.config['SQLALCHEMY_DATABASE_URI']='sqlite://///home/michael/geekdemos/geekapp/library.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db
= SQLAlchemy(app)
Ahora cree dos modelos para las tablas Usuarios y Autores como se muestra a continuación. Copie y pegue el código dentro del archivo app.py
Coloque el código de abajo justo debajo de esta configuración de la base de datos db = SQLAlchemy(app)
clase
Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
public_id = db.Column(db.Integer)
name = db.Column(db.String(50))
password = db.Column(db.String(50))
admin = db.Column(db.Boolean)
class Autores(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False))
book = db.Column(db.String(20), unique=True, nullable=False))
country = db.Column(db.String(50), nullable=False))
booker_prize = db.Column(db.Boolean)
Generar tablas de usuarios y autores
En el terminal, escriba el siguiente código dentro del entorno virtual para generar o crear las tablas de Usuarios y Autores como se muestra a continuación
from app import db
db
.create_all()
Después, abra el archivo app.py
dentro del entorno virtual y cree otra función
Esta función generará tokens para permitir que sólo los usuarios registrados puedan acceder y realizar un conjunto de operaciones de la API contra la tabla Autores
Coloque este código debajo del modelo de base de datos para la tabla Autores
def token_required(f):
@wraps(f)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'falta un token válido'})
try:
data = jwt.decode(token, app.config[SECRET_KEY])
current_user = Users.query.filter_by(public_id=data['public_id']).first()
except:
return jsonify({'mensaje': 'el token no es válido'})
return f(current_user, *args, **kwargs)
return decorator
Crear rutas para la tabla de usuarios
Ahora vamos a crear una ruta para permitir que los usuarios se registren en la API de Autores mediante un nombre de usuario y una contraseña, como se muestra a continuación
De nuevo abra el archivo app.py
dentro del entorno virtual y pegue el siguiente código debajo de la función token_required(f)
@app.route('/register', methods=['GET', 'POST'])
def signup_user():
data = request.get_json()
hashed_password = generate_password_hash(data['password'], method='sha256')
new_user = Users(public_id=str(uuid.uuid4()), name=datos['nombre'], password=contraseña_hashed, admin=False)
db.session.add(nuevo_usuario)
db.session.commit()
return jsonify({'mensaje': 'registrado correctamente'})
Dentro del entorno virtual, cree otra ruta en el archivo app.py
para permitir que los usuarios registrados inicien sesión
Cuando un usuario se registra, se genera un token aleatorio para que el usuario pueda acceder a la API de la biblioteca
Pegue el código siguiente debajo de la ruta anterior que hemos creado
@app.route('/login', methods=['GET', 'POST'])
def login_user():
auth = request.authorization
if not auth or not auth.username or not auth.password:
return make_response('could not verify', 401, {'WWW.Authentication': 'Basic realm: "login required"'})
user = Users.query.filter_by(name=auth.username).first()
if check_password_hash(user.password, auth.password):
token = jwt.encode({'public_id': user.public_id, 'exp' : datetime.datetime.utcnow() datetime.timedelta(minutes=30)}, app.config['SECRET_KEY'])
return jsonify({'token' : token.decode('UTF-8')})
return make_response('could not verify', 401, {'WWW.Authentication': 'Basic realm: "login requerido"'})
Aún así, dentro del entorno virtual, cree otra ruta en el archivo app.py
para obtener o recuperar todos los usuarios registrados
Este código comprueba todos los usuarios registrados en la tabla Usuarios y devuelve el resultado final en formato JSON
Pegue el código siguiente debajo de la ruta de inicio de sesión
@app.route('/users', methods=['GET'])
def get_all_users():
users = Users.query.all()
result = []
for user in users:
user_data = {}
user_data['public_id'] = user.public_id
user_data['name'] = user.name
user_data['password'] = user.password
user_data['admin'] = user.admin
result.append(user_data)
return jsonify({'users': result})
Crear rutas para la tabla de autores
Vamos a crear rutas para la tabla Autores para permitir a los usuarios recuperar todos los autores de la base de datos, así como eliminar autores
Sólo los usuarios con tokens válidos pueden realizar estas operaciones de la API
Dentro del archivo app.py, cree una ruta para que los usuarios registrados puedan crear nuevos autores
Pegue este código debajo de la ruta que permite a un usuario recuperar todos los usuarios registrados
@app.route('/author', methods=['POST', 'GET'])
@token_required
def create_author(current_user):
data = request.get_json()
new_authors = Authors(name=data['name'], country=data['country'], book=data['book'], booker_prize=True, user_id=current_user.id)
db.session.add(nuevos_autores)
db.session.commit()
return jsonify({'mensaje' : 'nuevo autor creado'})
A continuación, cree otra ruta para permitir que un usuario registrado con un token válido recupere todos los autores de la tabla Autores como se muestra a continuación
Pegue este código debajo de la ruta que permite a un usuario crear un nuevo autor
@app.route('/autores', methods=['POST', 'GET']) @token_required def get_authors(usuario_actual): authors = Autores.query.filter_by(id_usuario=id_actual.id).all() output = [] for author in autores: author_data = {} author_data['nombre'] = autor.name author_data['book'] = author.book author_data['country'] = author.country author_data['booker_prize'] = author.booker_prize output.append(author_data) return jsonify({'list_of_authors' : output})
Por último, aún dentro del archivo app.py
, cree una ruta para eliminar un autor especificado como se muestra a continuación
Pegue este código debajo de la ruta que permite a un usuario recuperar una lista de autores
@app.route('/autores/<id_autor>', methods=['BORRAR'])
@token_required
def borrar_autor(usuario_actual, autor_id):
author = Author.query.filter_by(id=author_id, user_id=current_user.id).first()
if not author:
return jsonify({'message': 'author does not exist'})
db.session.delete(author)
db.session.commit()
return jsonify({'message': 'Author deleted'})
if __name__ == '__main__':
app.run(debug=True)
Después, guarde y cierre el archivo app.py dentro del entorno virtual
Probar la API de la biblioteca con Postman
En esta sección, haremos uso de la herramienta postman para enviar una solicitud a los servicios de la base de datos. Si no dispone de postman en su máquina, puede averiguar cómo descargarlo e instalarlo aquí
Aparte del cartero, podemos hacer uso de otras herramientas como Rizar para enviar peticiones al servidor
Abra un nuevo terminal y escriba lo siguiente
postman
El comando postman
hará que su navegador web muestre la siguiente página
Puede decidir registrarse y crear una cuenta gratuita, pero nosotros la saltaremos y obtendremos acceso directo a la aplicación para probar la API de la biblioteca, como se muestra a continuación
En esta sección, permitiremos que un usuario se registre en la API de la biblioteca proporcionando un nombre de usuario y una contraseña única en formato JSON mediante el método POST siguiendo los pasos que se indican a continuación
- Haga clic en la pestaña denominada Cuerpo
- A continuación, seleccione el botón sin procesar y elija el formato JSON
- introduzca un nombre de usuario y una contraseña para registrarse como se muestra en la captura de pantalla
- Junto al botón de envío, inserte la siguiente URL http://127.0.0.1/register
- Por último, cambie el método a POST y pulse el botón enviar.
Aparecerá la siguiente salida como se muestra a continuación
Ahora hemos registrado con éxito a un usuario. Vamos a permitir que el usuario que se acaba de registrar inicie sesión con el fin de generar un token aleatorio temporal para acceder a la tabla Autores siguiendo los siguientes pasos
- Haga clic en la pestaña autorización.
- En la sección tipo, seleccione autenticación básica.
- A continuación, rellene el formulario de nombre de usuario y contraseña con el nombre de usuario y la contraseña con los que se registró anteriormente.
- Por último, pulse el botón enviar para acceder y generar un token aleatorio.
Una vez que el usuario haya iniciado sesión correctamente, se generará un token aleatorio para el usuario como se muestra en la captura de pantalla
Haremos uso del token aleatorio generado para acceder a la tabla Autores
En esta sección, añadiremos la información de un autor a la tabla Autores mediante el método POST siguiendo los siguientes pasos
- Haga clic en la pestaña de cabeceras
- Incluye las siguientes cabeceras HTTP mostradas en la captura de pantalla
- A continuación, haga clic en la pestaña cuerpo e introduzca los datos del nuevo autor
- A continuación, pulse el botón enviar para añadir los datos del autor a la tabla Autores
También puede recuperar la información de los autores en la tabla Autores de la siguiente manera
- Asegúrese de que su token generado se encuentra en la sección de encabezados. si no está ahí, deberá rellenarlo con su token.
- Junto al botón de envío, introduzca esta URL
http://127.0.0.1/authors
- A continuación, cambie el método HTTP a GET y pulse el botón de envío para recuperar los datos de los autores.
Por último, puede eliminar el autor o autores en la tabla Autores mediante el método DELETE
siguiendo los siguientes pasos
- Asegúrese de que su token sigue en la sección de cabeceras. Puede comprobar la pestaña de cabeceras para asegurarse de que la información necesaria está en su sitio.
- Junto al botón de envío, introduzca esta URL
http://127.0.0.1/sam
- A continuación, pulse el botón de envío para eliminar el usuario que ha especificado.
Puede encontrar el código fuente completo en Github. Puede clonarlo y comprobarlo en su máquina.