La seguridad no es una funcionalidad que se "añade al final": es una propiedad transversal que debe estar presente desde la primera línea del diseño. Esto es lo que significa seguridad por diseño (security by design). Un sistema que funciona, escala y está siempre disponible no sirve de nada si un atacante puede robar los datos de los usuarios o suplantar su identidad. En esta lección aprenderás los principios fundamentales de la seguridad (defensa en profundidad y mínimo privilegio), la diferencia esencial entre autenticación y autorización, los protocolos modernos que las implementan (OAuth2, OIDC y JWT), las vulnerabilidades más frecuentes según OWASP y, por último, cómo proteger los datos mediante cifrado en tránsito y en reposo.
Nota profesional: el contenido de esta lección es educativo sobre arquitectura. Cualquier implementación real de seguridad en un sistema de producción debe ser revisada por especialistas en seguridad y cumplir la normativa aplicable (por ejemplo GDPR/LOPDGDD).
Contenido
- Principios: defensa en profundidad y mínimo privilegio
- Autenticación vs Autorización
- OAuth2, OpenID Connect y JWT
- OWASP Top 10 resumido
- Cifrado en tránsito y en reposo
- Errores comunes y consejos
- Ejercicios
- Principios: defensa en profundidad y mínimo privilegio
Dos principios sostienen toda arquitectura segura.
La defensa en profundidad consiste en no confiar en una única barrera, sino en colocar múltiples capas de seguridad independientes. Si una falla, las demás siguen protegiendo. Es el principio del castillo medieval: foso, muralla, puertas, torres.
graph LR
A[Internet] --> F[Firewall / WAF]
F --> G[API Gateway + Auth]
G --> S[Validación en el servicio]
S --> D[(BD con permisos mínimos)]Cada capa del diagrama protege de forma independiente: aunque un atacante atraviese el firewall, todavía debe superar la autenticación del gateway, la validación del servicio y los permisos de la base de datos.
El mínimo privilegio (least privilege) establece que cada usuario, servicio o proceso debe tener solo los permisos estrictamente necesarios para su función, ni uno más. Una cuenta de servicio que solo lee informes no debe poder borrar tablas.
-- MAL: la aplicación usa un usuario todopoderoso GRANT ALL PRIVILEGES ON *.* TO 'app'@'%'; -- BIEN: solo los permisos que realmente necesita, sobre la base concreta GRANT SELECT, INSERT, UPDATE ON tienda.* TO 'app'@'10.0.%';
En el ejemplo "MAL", si comprometen la aplicación, el atacante tiene control total de todas las bases de datos. En el "BIEN", solo puede leer y escribir en tienda, ni siquiera borrar (DELETE), y solo desde la red interna 10.0.%. El daño potencial queda contenido.
- Autenticación vs Autorización
Estos dos conceptos se confunden constantemente, pero son distintos:
| Autenticación (AuthN) | Autorización (AuthZ) | |
|---|---|---|
| Pregunta | ¿Quién eres? | ¿Qué puedes hacer? |
| Verifica | Identidad | Permisos |
| Ocurre | Primero | Después (ya identificado) |
| Ejemplo | Iniciar sesión con usuario y contraseña | "¿Puede este usuario borrar facturas?" |
| Resultado típico | Un token de identidad | Permitir o denegar la operación |
La regla mnemotécnica: AuthN = autenticNación (quién eres); AuthZ = autoriZación (qué puedes). Siempre se autentica primero y se autoriza después.
Los modelos de autorización más habituales son:
- RBAC (Role-Based Access Control): los permisos se asignan a roles (admin, editor, lector) y los usuarios reciben roles. Simple y muy extendido.
- ABAC (Attribute-Based Access Control): la decisión se basa en atributos (departamento, hora, ubicación, antigüedad). Más flexible y granular, pero más complejo.
- OAuth2, OpenID Connect y JWT
Estos tres términos suelen mezclarse. La clave para no confundirse:
| Tecnología | Para qué sirve | Responde a |
|---|---|---|
| OAuth 2.0 | Autorización delegada (dar acceso a recursos) | ¿Qué puede hacer esta app en tu nombre? |
| OpenID Connect (OIDC) | Autenticación (capa sobre OAuth2) | ¿Quién es el usuario? |
| JWT | Formato de token (transporta la info) | (No es un protocolo, es un contenedor) |
OAuth 2.0 permite que una aplicación acceda a recursos en nombre del usuario sin conocer su contraseña (por ejemplo, "Iniciar sesión con Google"). OIDC se construye encima de OAuth2 y añade la pieza que le faltaba: identificar al usuario, mediante un ID Token. JWT (JSON Web Token) es el formato más común en el que viajan esos tokens.
Un JWT tiene tres partes separadas por puntos: cabecera.payload.firma.
// Cabecera (header): algoritmo y tipo
{ "alg": "RS256", "typ": "JWT" }
// Payload (claims): la información, NO cifrada, solo codificada en Base64
{
"sub": "usuario-123", // subject: identificador del usuario
"name": "Ana García",
"roles": ["editor"], // usado para autorización
"iat": 1718000000, // issued at: cuándo se emitió
"exp": 1718003600 // expiration: cuándo caduca (1 hora después)
}Punto crítico que confunde a los principiantes: el payload de un JWT no está cifrado, solo codificado en Base64. Cualquiera puede leerlo. Lo que garantiza la firma (la tercera parte) es la integridad y autenticidad: que nadie ha modificado el contenido y que lo emitió quien dice ser. Por eso nunca se deben poner secretos en un JWT.
// Validar un JWT firmado con RS256 (clave pública/privada)
Claims claims = Jwts.parserBuilder()
.setSigningKey(clavePublica) // Verificamos la firma con la clave pública del emisor
.build()
.parseClaimsJws(tokenRecibido) // Lanza excepción si la firma o la expiración no son válidas
.getBody();
String userId = claims.getSubject(); // Extraemos el "sub"
List<String> roles = claims.get("roles", List.class); // y los roles para autorizarEste código verifica que el token no ha sido manipulado (gracias a la firma) ni ha caducado (exp), y solo entonces extrae el identificador y los roles. Si la validación falla, lanza una excepción y se rechaza la petición.
- OWASP Top 10 resumido
OWASP (Open Worldwide Application Security Project) publica periódicamente el Top 10 de los riesgos de seguridad web más críticos. Es lectura obligada. Resumen de las categorías principales:
| # | Categoría | En qué consiste | Mitigación clave |
|---|---|---|---|
| A01 | Pérdida de control de acceso | Acceder a recursos sin permiso | Autorización en el servidor, denegar por defecto |
| A02 | Fallos criptográficos | Datos sensibles mal protegidos | Cifrar en tránsito y reposo, no inventar criptografía |
| A03 | Inyección (SQL, etc.) | Datos maliciosos como código | Consultas parametrizadas, validar entradas |
| A04 | Diseño inseguro | Fallos de diseño, no de código | Modelado de amenazas desde el diseño |
| A05 | Configuración incorrecta | Valores por defecto, errores expuestos | Endurecer (hardening), revisar configuración |
| A06 | Componentes vulnerables | Librerías obsoletas con CVE | Actualizar, escanear dependencias |
| A07 | Fallos de autenticación | Sesiones débiles, fuerza bruta | MFA, límites de intentos, gestión de sesión segura |
| A08 | Fallos de integridad | Datos/código sin verificar | Firmas, verificar la cadena de suministro |
| A09 | Fallos de registro/monitorización | No detectar ataques | Logs de seguridad, alertas, observabilidad |
| A10 | SSRF | Forzar al servidor a pedir URLs internas | Validar y restringir destinos de las peticiones |
El ejemplo clásico es la inyección SQL (A03):
// MAL: concatenar la entrada del usuario directamente en la consulta
String sql = "SELECT * FROM usuarios WHERE email = '" + email + "'";
// Si email = "' OR '1'='1", la consulta devuelve TODOS los usuarios.
// BIEN: consulta parametrizada; el motor trata "email" como dato, nunca como código
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM usuarios WHERE email = ?");
ps.setString(1, email); // El driver escapa el valor de forma seguraEn la versión "MAL", un atacante puede inyectar SQL en el campo email y manipular la consulta. En la "BIEN", el ? es un marcador de posición: el motor nunca interpreta el valor del usuario como instrucciones SQL, eliminando la vulnerabilidad de raíz.
- Cifrado en tránsito y en reposo
Los datos deben protegerse en sus dos estados:
- En tránsito (in transit): mientras viajan por la red. Se protege con TLS (el candado de HTTPS). Evita que alguien que intercepte el tráfico pueda leerlo (man in the middle).
- En reposo (at rest): mientras están almacenados en disco, base de datos o copias de seguridad. Se protege cifrando el almacenamiento o columnas concretas.
| En tránsito | En reposo | |
|---|---|---|
| Amenaza | Interceptación en la red | Robo del disco o backup |
| Tecnología | TLS / HTTPS | Cifrado de disco/columna (AES) |
| Ejemplo | Conexión https:// |
Base de datos con cifrado transparente (TDE) |
# Forzar HTTPS y redirigir todo el tráfico HTTP en Nginx
server {
listen 80;
return 301 https://$host$request_uri; # Cualquier petición HTTP se redirige a HTTPS
}
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3; # Solo versiones modernas y seguras de TLS
ssl_certificate /etc/ssl/cert.pem;
ssl_certificate_key /etc/ssl/key.pem;
}Este fragmento garantiza que nunca se sirva contenido por HTTP sin cifrar: el primer bloque redirige (código 301) todo el tráfico del puerto 80 a HTTPS. El segundo configura TLS permitiendo solo las versiones 1.2 y 1.3 (las antiguas tienen vulnerabilidades conocidas).
Una regla esencial sobre contraseñas: nunca se cifran ni se guardan en texto plano; se aplican funciones de hash lentas y con sal (bcrypt, scrypt, Argon2). Así, aunque roben la base de datos, no pueden recuperar las contraseñas originales.
Errores Comunes y Consejos
- Confiar solo en el cliente. La validación y la autorización del navegador o la app móvil se pueden saltar trivialmente. Todo se valida y autoriza en el servidor.
- Creer que el JWT está cifrado. No lo está. No metas datos sensibles en el payload; cualquiera puede leerlo.
- Inventar tu propia criptografía. Casi siempre termina mal. Usa librerías y protocolos estándar, revisados y mantenidos.
- Guardar contraseñas con MD5/SHA o en claro. Usa funciones de hash diseñadas para contraseñas (bcrypt, Argon2) con sal.
- Mensajes de error demasiado detallados. Decir "el usuario no existe" frente a "contraseña incorrecta" ayuda al atacante. Responde de forma genérica.
- No actualizar dependencias. Las librerías obsoletas (A06) son una de las vías de ataque más frecuentes. Escanea y actualiza.
- Consejo: piensa como un atacante. En cada entrada de datos pregúntate "¿qué pasa si esto es malicioso?".
Ejercicios
-
AuthN vs AuthZ. Un usuario inicia sesión correctamente pero, al intentar acceder al panel de administración, recibe un error 403 (Prohibido). ¿Falló la autenticación o la autorización? Justifícalo.
-
Detectar la vulnerabilidad. Revisa este código y di qué riesgo del OWASP Top 10 contiene y cómo lo corregirías:
String consulta = "SELECT * FROM productos WHERE nombre = '" + busqueda + "'"; statement.executeQuery(consulta); -
Decisión de cifrado. Una aplicación de mensajería envía mensajes entre usuarios y los almacena en su base de datos. ¿Qué tipos de cifrado necesita y por qué cada uno?
Soluciones
-
Falló la autorización (AuthZ), no la autenticación. El error 403 (Prohibido) significa que el sistema sabe quién es el usuario (se autenticó bien, no es un 401), pero ese usuario no tiene permisos para acceder al panel de administración. Si hubiera fallado la autenticación, el código sería 401 (No autorizado/no identificado).
-
Es inyección SQL (A03): la variable
busquedase concatena directamente en la consulta, permitiendo a un atacante manipular el SQL. Corrección con consulta parametrizada:PreparedStatement ps = conn.prepareStatement( "SELECT * FROM productos WHERE nombre = ?"); ps.setString(1, busqueda); ps.executeQuery();El
?impide que el valor del usuario se interprete como código SQL. -
Necesita ambos. Cifrado en tránsito (TLS/HTTPS) para que nadie pueda interceptar los mensajes mientras viajan por la red entre el dispositivo y el servidor. Y cifrado en reposo para proteger los mensajes almacenados en la base de datos frente al robo del disco o de una copia de seguridad. (Las apps más exigentes añaden cifrado de extremo a extremo, donde ni el propio servidor puede leerlos.)
Conclusión
Has aprendido que la seguridad se diseña desde el principio mediante capas (defensa en profundidad) y permisos mínimos, que autenticar (quién eres) es distinto de autorizar (qué puedes), que OAuth2/OIDC/JWT son las piezas estándar de la identidad moderna, que el OWASP Top 10 cataloga los riesgos que debes conocer, y que los datos se cifran tanto en tránsito como en reposo. La seguridad nunca es un producto terminado: es un proceso continuo de prevención, detección y mejora.
Y para detectar tanto ataques como problemas de rendimiento necesitamos ver qué ocurre dentro del sistema. En la siguiente lección, Observabilidad: Logging, Métricas y Trazabilidad, aprenderemos a instrumentar la aplicación para entender su comportamiento en producción.
Curso de Arquitectura de Aplicaciones
Módulo 1: Fundamentos de la Arquitectura de Aplicaciones
- ¿Qué es la Arquitectura de Aplicaciones?
- El Rol del Arquitecto de Software
- Atributos de Calidad y Requisitos No Funcionales
- Decisiones Arquitectónicas y Compromisos (Trade-offs)
- Documentación de Arquitectura: Vistas y el Modelo C4
Módulo 2: Principios y Tácticas de Diseño
- Acoplamiento, Cohesión y Separación de Responsabilidades
- Principios SOLID Aplicados a la Arquitectura
- DRY, KISS, YAGNI y Otros Principios de Diseño
- Tácticas Arquitectónicas para los Atributos de Calidad
- Gestión de la Deuda Técnica
Módulo 3: Estilos y Patrones Arquitectónicos
- Arquitectura Monolítica
- Arquitectura en Capas (N-Tier)
- Arquitectura Cliente-Servidor
- Arquitectura Hexagonal (Puertos y Adaptadores)
- Arquitectura Limpia y Cebolla (Clean & Onion)
Módulo 4: Arquitecturas Distribuidas y Microservicios
- Introducción a los Sistemas Distribuidos
- Arquitectura de Microservicios
- Descomposición de Servicios y Bounded Contexts
- API Gateway, Service Discovery y Comunicación entre Servicios
- Patrones de Resiliencia: Circuit Breaker, Retry y Bulkhead
- El Teorema CAP y la Consistencia de Datos
Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería
- Fundamentos de la Arquitectura Orientada a Eventos
- Mensajería Asíncrona: Colas y Brokers
- Patrones de Eventos: Event Sourcing y CQRS
- Gestión de Transacciones Distribuidas: Patrón Saga
- Streaming de Datos en Tiempo Real
Módulo 6: Diseño Dirigido por el Dominio (DDD)
- Conceptos Fundamentales del DDD
- Diseño Estratégico: Bounded Contexts y Lenguaje Ubicuo
- Diseño Táctico: Entidades, Agregados y Repositorios
- Mapeo de Contextos (Context Mapping)
Módulo 7: Datos y Persistencia
- Estrategias de Persistencia: SQL vs NoSQL
- Patrones de Acceso a Datos: Repository, Unit of Work y DAO
- Base de Datos por Servicio y Gestión de Datos Distribuidos
- Caché y Estrategias de Invalidación
Módulo 8: Arquitectura en la Nube y Despliegue
- Fundamentos del Cloud Computing (IaaS, PaaS, SaaS)
- Contenedores y Orquestación con Docker y Kubernetes
- Arquitectura Serverless
- Patrones de Diseño Cloud-Native
- Infraestructura como Código (IaC)
Módulo 9: Calidad, Seguridad y Observabilidad
- Escalabilidad: Horizontal vs Vertical y Balanceo de Carga
- Alta Disponibilidad y Tolerancia a Fallos
- Seguridad por Diseño y Autenticación/Autorización
- Observabilidad: Logging, Métricas y Trazabilidad
- Rendimiento y Pruebas de Carga
