Skip to main content

Retornar datos de sesión

Cuando un usuario o empleado inicia sesión en la plataforma y crea algún tipo de registro, por ejemplo una solicitud de recursos humanos, la aplicación que realiza la inserción necesita saber qué usuario autenticado está realizando la operación.

En esta práctica, la autenticación la gestiona Flask, pero la inserción puede realizarla una aplicación Java/Tomcat o PHP. Para resolverlo, Flask devolverá los datos básicos de sesión al reverse proxy, y Nginx Proxy Manager los reenviará a la aplicación mediante cabeceras HTTP.

El flujo general será:

Usuario inicia sesión en Flask

Flask guarda en sesión user_id, user y roles

El usuario accede a una ruta protegida

Nginx pregunta a Flask si puede acceder

Flask responde con X-User-Id, X-Username y X-Role

Nginx reenvía esas cabeceras a Java/PHP

Java/PHP usa el id del usuario en sus operaciones

La responsabilidad queda dividida de esta forma:

Flask
- Autentica al usuario.
- Guarda la sesión.
- Guarda user_id, user y roles.
- Responde a /auth/verify.

Nginx Proxy Manager
- Protege rutas.
- Pregunta a Flask si el usuario tiene permiso.
- Recoge X-User-Id, X-Username y X-Role.
- Reenvía esas cabeceras a Java o PHP.

Java/PHP
- No gestionan el login.
- Leen X-User-Id, X-Username y X-Role.
- Usan el id del usuario para guardar quién realiza cada operación.

Video de ayuda


1. Cambiar la función SQL

La función login_empleado inicialmente devuelve un valor de tipo BOOLEAN:

  • true si el login es correcto.
  • false si el login es incorrecto.

Para poder conocer el identificador del empleado autenticado, se modifica la función para que devuelva un INTEGER con el id del empleado.

Si el login es incorrecto, devolverá NULL.

warning

Si ya existe la función anterior con el mismo nombre y parámetros, puede ser necesario eliminarla primero, ya que PostgreSQL no siempre permite cambiar directamente el tipo de retorno con CREATE OR REPLACE.

DROP FUNCTION IF EXISTS login_empleado(VARCHAR, VARCHAR);

Nueva función:

CREATE OR REPLACE FUNCTION login_empleado(
_login VARCHAR,
_passwd VARCHAR
)
RETURNS INTEGER
AS $$
DECLARE
v_id INTEGER;
v_passwd_almacenada VARCHAR(255);
BEGIN
SELECT id, passwd
INTO v_id, v_passwd_almacenada
FROM empleados
WHERE correo = _login OR nombre = _login
LIMIT 1;

IF NOT FOUND THEN
RETURN NULL;
END IF;

IF crypt(_passwd, v_passwd_almacenada) = v_passwd_almacenada THEN
RETURN v_id;
END IF;

RETURN NULL;
END;
$$ LANGUAGE plpgsql;

Con este cambio:

Login correcto → devuelve el id del empleado
Login incorrecto → devuelve NULL

2. Modificar la función login_empleado en Flask

En Flask, la función Python puede mantenerse, pero ahora su resultado será el id del empleado o None.

def login_empleado(user, passwd):
"""Devuelve el id del empleado si el login es correcto; si no, devuelve None."""
with get_conn() as conn:
with conn.cursor() as cur:
cur.execute("SELECT login_empleado(%s, %s);", (user, passwd))
return cur.fetchone()[0]

3. Guardar el id en la sesión de Flask

En el login de Flask, cuando el usuario se autentica correctamente, se almacena el identificador del empleado en la sesión.

empleado_id = login_empleado(user, passwd)

if empleado_id is not None:
session.permanent = True
session["login"] = True
session["user"] = user
session["user_id"] = empleado_id
session["roles"] = ["empleado"]

if next_url_segura(next_url):
return redirect(next_url)

return redirect(url_for("index"))

Para el usuario administrador definido mediante variables de entorno:

if user == USUARIO and passwd == PASSWD:
session.permanent = True
session["login"] = True
session["user"] = user
session["user_id"] = 0
session["roles"] = ["admin"]

if next_url_segura(next_url):
return redirect(next_url)

return redirect(url_for("index"))

4. Devolver datos de sesión desde /auth/verify

Para que Nginx Proxy Manager pueda pasar los datos del usuario autenticado a Java o PHP, el endpoint /auth/verify debe devolver cabeceras HTTP.

Primero se añade make_response al import de Flask:

from flask import Flask, render_template, request, redirect, url_for, session, jsonify, make_response

Después se modifica el endpoint /auth/verify:

@app.route("/auth/verify")
def auth_verify():
"""
Endpoint para validación desde reverse proxy.

Respuestas:
- 204: permitido.
- 401: no autenticado.
- 403: autenticado, pero sin rol requerido.
"""
if not session.get("login"):
return "", 401

roles_usuario = session.get("roles", [])

if isinstance(roles_usuario, str):
roles_usuario = [roles_usuario]

def respuesta_permitida():
response = make_response("", 204)
response.headers["X-User-Id"] = str(session.get("user_id", ""))
response.headers["X-Username"] = session.get("user", "")
response.headers["X-Role"] = ",".join(roles_usuario)
return response

roles_requeridos = request.headers.get("X-Required-Roles", "").strip()

if not roles_requeridos:
return respuesta_permitida()

lista_roles_requeridos = [
rol.strip()
for rol in roles_requeridos.split(",")
if rol.strip()
]

for rol in lista_roles_requeridos:
if rol in roles_usuario:
return respuesta_permitida()

return "", 403

De esta forma, cuando un usuario autenticado tenga permiso, Flask devolverá cabeceras como estas:

X-User-Id: 3
X-Username: gaspar
X-Role: empleado

5. Configuración general del reverse proxy

Esta configuración se coloca en la sección avanzada/general del Proxy Host de Nginx Proxy Manager.

MUY IMPORTANTE
  • Cambia practica4-flaskapp-1 por el nombre real del contenedor o servicio de Flask en tu stack.
  • Puedes comprobar los nombres disponibles con:
docker network inspect NOMBRE_RED
  • El rol indicado en X-Required-Roles debe coincidir exactamente con el rol guardado en Flask.
  • Por ejemplo, si Flask guarda session["roles"] = ["empleado"], Nginx debe pedir empleado, no usuario ni cliente.
location = /_flask_auth_admin {
internal;

proxy_pass http://practica4-flaskapp-1:8888/auth/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";

proxy_set_header Cookie $http_cookie;
proxy_set_header Authorization $http_authorization;

proxy_set_header X-Required-Roles "admin";

proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Host $host;
proxy_set_header X-Original-Method $request_method;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location = /_flask_auth_empleado {
internal;

proxy_pass http://practica4-flaskapp-1:8888/auth/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";

proxy_set_header Cookie $http_cookie;
proxy_set_header Authorization $http_authorization;

proxy_set_header X-Required-Roles "empleado";

proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Host $host;
proxy_set_header X-Original-Method $request_method;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location @login_redirect {
return 302 /login?next=$request_uri;
}

location @forbidden {
return 403;
}
no ia

6. Configurar una ruta protegida para empleado

En cada ruta que deba ser accesible por empleados, se añade:

auth_request /_flask_auth_empleado;

auth_request_set $auth_user_id $upstream_http_x_user_id;
auth_request_set $auth_username $upstream_http_x_username;
auth_request_set $auth_role $upstream_http_x_role;

error_page 401 = @login_redirect;
error_page 403 = @forbidden;

proxy_set_header X-User-Id $auth_user_id;
proxy_set_header X-Username $auth_username;
proxy_set_header X-Role $auth_role;

Por ejemplo, para una página Java de empleado:

/vistas/protegida_usuario.jsp
no ia

7. Configurar una ruta protegida para administrador

En cada ruta que deba ser accesible solo por administradores, se añade:

auth_request /_flask_auth_admin;

auth_request_set $auth_user_id $upstream_http_x_user_id;
auth_request_set $auth_username $upstream_http_x_username;
auth_request_set $auth_role $upstream_http_x_role;

error_page 401 = @login_redirect;
error_page 403 = @forbidden;

proxy_set_header X-User-Id $auth_user_id;
proxy_set_header X-Username $auth_username;
proxy_set_header X-Role $auth_role;

Por ejemplo:

/vistas/protegida_admin.jsp
no ia

8. Obtener datos de sesión en Java

En Java, los datos se obtienen desde las cabeceras HTTP que ha añadido Nginx después de validar la sesión en Flask.

En un controlador o servlet:

String userIdHeader = request.getHeader("X-User-Id");
String username = request.getHeader("X-Username");
String roleHeader = request.getHeader("X-Role");

if (userIdHeader == null || userIdHeader.isBlank()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Usuario no autenticado");
return;
}

int idUsuario = Integer.parseInt(userIdHeader);

9. Uso correcto con DAO

No es recomendable leer las cabeceras HTTP directamente dentro del DAO.

El DAO no debería conocer request, porque request pertenece a la capa web.

La forma correcta es:

Controlador lee X-User-Id

Controlador convierte el id a int

Controlador llama al DAO pasando idUsuario

DAO usa idUsuario en el INSERT

Ejemplo de controlador:

String userIdHeader = request.getHeader("X-User-Id");

if (userIdHeader == null || userIdHeader.isBlank()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Usuario no autenticado");
return;
}

int idUsuario = Integer.parseInt(userIdHeader);

String titulo = request.getParameter("titulo");
String descripcion = request.getParameter("descripcion");

SolicitudDAO dao = new SolicitudDAO();
dao.insertarSolicitud(titulo, descripcion, idUsuario);

Ejemplo de DAO:

public void insertarSolicitud(String titulo, String descripcion, int idUsuario) throws SQLException {
String sql = """
INSERT INTO solicitudes (titulo, descripcion, empleado_id)
VALUES (?, ?, ?)
""";

try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {

ps.setString(1, titulo);
ps.setString(2, descripcion);
ps.setInt(3, idUsuario);

ps.executeUpdate();
}
}