CodeGym /Cursos /Spring Security /Documentación de API y endpoints de utilidad

Documentación de API y endpoints de utilidad

Spring Security
Nivel 18, Lección 2
Disponible

1. Documentación y «servicios internos» en el modelo de seguridad

Cuando desarrollas un backend, la mente del principiante suele dibujar un panorama así: «aquí están mis controladores, aquí están mis /api/..., y eso es lo que hay que proteger». Es natural: tú mismo escribiste esos endpoints, los ves todos los días, ejecutas pruebas sobre ellos. El problema es que en un proyecto moderno de Spring Boot, alrededor de la API de negocio casi siempre hay varias superficies HTTP adicionales, y también viven en la misma aplicación, en el mismo dominio, con los mismos riesgos.

Hasta ahora, el mapa de acceso giraba principalmente en torno a /api/**: público, zona de usuario, rutas privilegiadas. Pero la aplicación no termina en los controladores de negocio. Si los docs y actuator no están incluidos en ese mismo mapa, quedan entradas HTTP que la configuración de seguridad simplemente olvidó.

Las superficies «olvidadas» más típicas son la documentación (swagger-ui, api-docs) y Actuator (/actuator/...). A menudo se conectan «por un momento», «solo para desarrollo», «lo cerramos después», y luego llega ese «después» que de repente se convierte en «nunca». Y así tienes no solo una API, sino una API con un escaparate interactivo de todos los endpoints y un «panel de control» de diagnóstico al alcance.

Si hacemos una analogía cotidiana, los endpoints de negocio son la puerta principal y las ventanas que pusiste conscientemente. Swagger UI es el expositor con el plano del edificio, donde está marcado dónde está la caja fuerte. Actuator es la entrada de servicio con el cartel de «personal», al lado del cual a veces se olvidan de poner un candado. En seguridad, no podemos permitirnos pensar solo en la puerta principal.

Docs: interfaz de usuario (UI) y OpenAPI JSON

La documentación de la API en el mundo Spring suele referirse a la descripción OpenAPI y a la interfaz de usuario que la muestra. En realidad, son dos conjuntos diferentes de endpoints, y es útil distinguirlos desde el principio para no confundirse en la política de acceso. Un conjunto es la «descripción para máquinas» de la API (JSON), el otro es el «escaparate para humanos» (HTML/JS).

Normalmente, tanto en proyectos educativos como en proyectos reales con Spring Boot, la documentación la proporciona Springdoc. Entonces las rutas estándar suelen ser: "/v3/api-docs/**" para el JSON de OpenAPI y "/swagger-ui/**" para la interfaz de usuario. El primer endpoint no solo es útil para el navegador, sino también para herramientas: generación de clientes, verificación de contratos, automatización. El segundo es simplemente una interfaz cómoda donde puedes probar los endpoints y ver las respuestas.

Y aquí está la pregunta clave: ¿deberían estos endpoints ser públicos en absoluto? El principiante a veces se siente tentado a hacer permitAll(), porque «bueno, es documentación, no son datos». Pero la documentación casi siempre revela la estructura de tu sistema: qué endpoints hay, qué DTOs, qué códigos de error, qué parámetros. No es un «secreto», pero es un gran regalo para un atacante: en lugar de adivinar, obtiene la lista completa del menú.

Comparemos brevemente la interfaz de usuario y la descripción JSON:

Superficie Ejemplos de rutas Qué devuelve Por qué puede ser sensible
OpenAPI JSON /v3/api-docs/** JSON con el esquema de la API Lista de endpoints, estructuras DTO, a veces ejemplos, descripción de errores
Swagger UI /swagger-ui/** Interfaz HTML/JS Un «panel de control» conveniente desde el que es fácil ejecutar la API manualmente

3. Política de acceso a los docs

Ahora no hacemos magia, sino una decisión de ingeniería. La política de acceso no es «qué anotación es más bonita», sino la respuesta a la pregunta: ¿quién necesita esta superficie y para qué? En nuestro proyecto de aprendizaje, Secure Content Platform API, necesitamos la documentación nosotros (desarrolladores/estudiantes) para consultar el contrato. Pero no es un portal público de documentación del producto para el mundo exterior, cuyo objetivo sea atraer integradores.

Por lo tanto, una buena política base para el curso es: los docs solo están disponibles para usuarios autenticados (o incluso solo para administradores/editores, si se quiere ser más estricto), y un usuario anónimo no los necesita. Esta decisión disciplina bien, porque ves inmediatamente: «los docs son parte de la aplicación y tienen reglas». En la vida real, a veces ocurre lo contrario (API pública con documentación pública), pero ese es otro contexto.

Es importante no caer en el extremo: cerrar los docs de tal manera que ni siquiera tú puedas usarlos. Si proteges Swagger UI, piensa cómo te vas a autenticar en el navegador. En esta rama del curso ya aprendimos a hacer errores de seguridad amigables para REST, pero los docs son justo el lugar donde el navegador y el HTML vuelven a aparecer. No hay una única respuesta «correcta», pero hay una regla de sentido común: la política debe ser clara y predecible.

La línea base más simple que mostraremos hoy es: authenticated() en ambos endpoints de docs. No porque sea «lo más seguro siempre», sino porque es «el mínimo razonable para un backend educativo» que no convierte la documentación en una superficie accidentalmente pública.

4. Docs en SecurityFilterChain

Pasamos a la parte práctica. No queremos que las reglas para los docs se pierdan en medio del bloque /api/**, porque los docs normalmente viven fuera de /api. Es importante que la configuración se lea como un mapa: primero la API pública, luego las zonas de usuario/editor/admin, luego los docs, luego los endpoints de servicio, y finalmente la regla de cierre. Esto recuerda a un mapa de metro bien señalizado: no hay que «adivinar» hacia dónde va la línea.

A continuación, un fragmento mínimo del mismo mapa de acceso. Observa que agrupamos las rutas en una constante para no duplicar cadenas por todo el código. Es un pequeño hábito que ahorra muchos dolores de cabeza cuando, dentro de un mes, cambias una ruta o añades otro endpoint de docs.

// por ejemplo, junto a los otros grupos de rutas
private static final String[] DOCS = {
    "/swagger-ui/**",
    "/v3/api-docs/**"
};

// dentro de authorizeHttpRequests(auth -> auth ...)

// Docs es una zona separada y tiene su propia regla.
auth.requestMatchers(DOCS).authenticated();

Esta es una pieza local de la misma cadena. Es importante ver la política en sí: docs es otra superficie HTTP de la aplicación y no debe quedar fuera del mapa de acceso general.

Ahora un matiz importante para el principiante. Si más abajo en la configuración tienes algo como "/**".permitAll(), entonces acabas de explicar cuidadosamente la seguridad... y luego la cancelaste con tus propias manos. Por lo tanto, la regla de docs siempre debe vivir en una configuración donde entiendas lo que viene después y donde haya una regla de cierre (normalmente anyRequest().denyAll()).

5. Actuator: endpoints y riesgos de acceso

Actuator es un conjunto de endpoints HTTP de servicio de Spring Boot, diseñado para la operación: salud de la aplicación, información de versión, métricas, a veces configuración de logging, a veces volcados de hilos, etc. Al principiante le parece «algo para el devops», y por eso es fácil olvidarlo en el modelo de seguridad. Pero este es exactamente el caso en que «no es de negocio» no significa «no es peligroso».

La característica más desagradable de Actuator es que entre sus endpoints hay algunos completamente inofensivos (/actuator/health), y otros que, en acceso público, se convierten en una búsqueda del tesoro de «adivina las entrañas del servicio». Por ejemplo, dependiendo de la configuración, pueden estar disponibles datos sobre el entorno, los beans, la configuración de logs. Incluso si no hay contraseñas directas (y a veces, por desgracia, las hay), esa información facilita enormemente un ataque: el atacante obtiene un mapa de la cocina interna.

Por lo tanto, una política razonable es: permitir el mínimo, cerrar el resto explícitamente. En un proyecto educativo, es útil abrir health (a veces un monitor externo necesita leerlo) y, por ejemplo, hacer que info solo esté disponible para usuarios autenticados. Todo lo demás es mejor cerrarlo con denyAll() a nivel de reglas de solicitud, incluso si Actuator no está conectado actualmente: es un «seguro contra el futuro».

6. Lista blanca (allow-list) para Actuator

Ahora juntemos esto en una regla clara. Queremos que la configuración se lea como «permitir lo concreto, denegar el resto». Eso es el enfoque de lista blanca. En seguridad, casi siempre es preferible a «permitir todo y luego tapar agujeros».

Aquí hay un ejemplo compacto de reglas para la zona de actuator:

// dentro de authorizeHttpRequests(auth -> auth ...)

// Permitir solo lo que realmente se necesita desde fuera (por ejemplo, monitorización).
auth.requestMatchers("/actuator/health").permitAll();

// Información del servicio (versión, build) — solo después de autenticación.
auth.requestMatchers("/actuator/info").authenticated();

// Todo lo demás se cierra explícitamente para que no «aparezca» inesperadamente en el futuro.
auth.requestMatchers("/actuator/**").denyAll();

Observa la lógica: primero los permisos más específicos, luego una denegación amplia. Si cambiamos el orden y ponemos "/actuator/**".denyAll() primero, los permisos posteriores no funcionarán. Es el mismo principio de orden que en los matchers de solicitud normales: primero la regla específica, luego el catch-all amplio.

Otro matiz: usamos deliberadamente denyAll(), en lugar de confiar en que «por defecto está cerrado». Cuando dentro de seis meses abras la configuración, no tendrás que adivinar qué quiso decir el autor. Verás la intención: «Actuator está cerrado, excepto health e info».

7. Comportamiento 401/403 para docs y actuator

Es importante hablar de lo que ocurre cuando alguien accede a los docs o a actuator sin permisos. Ya tenemos configurados manejadores REST-friendly para 401/403 a través de AuthenticationEntryPoint y AccessDeniedHandler. Lo más probable es que devuelvan un contrato de error JSON. Y aquí el principiante puede entrar en pánico: «¡Pero Swagger UI es HTML! ¿Para qué quiere JSON?»

La respuesta es pragmática: ahora estamos construyendo seguridad para un backend API, y la uniformidad es más importante que la estética. Si has cerrado swagger-ui y recibes JSON en lugar de una página bonita, no es un «error de Spring», es una señal de tu política: «el acceso a los docs solo después de autenticación». En producción se puede hacer un modelo más amigable (por ejemplo, docs solo disponibles en el perfil de desarrollo, o accesibles desde un dominio separado), pero en un curso de fundamentos es más importante aprender a controlar la superficie de acceso que a pulir la UX de la documentación.

Además, /v3/api-docs/** es un endpoint JSON por naturaleza. Un error JSON ahí se ve completamente natural. Y para /actuator/**, JSON tampoco es un problema: estos endpoints a menudo no los consumen navegadores, sino monitores y herramientas que también prefieren una respuesta legible por máquina.

Si realmente se desea una reacción «bonita» en Swagger UI, eso ya es una decisión de diseño de nivel «cómo nos autenticamos en el navegador» y «qué cliente consume los docs». No profundizamos en eso hoy a propósito, para no desviarnos hacia temas vecinos.

8. Agrupamos las reglas de docs y actuator

El último fragmento práctico: cómo hacer que la configuración no se convierta en un muro de cadenas. Cuando aparecen PUBLIC_ARTICLES, DOCS, ACTUATOR y luego docenas de reglas más, la mente empieza a cansarse. Aquí ayuda una técnica simple: agrupar las rutas en constantes y mantener los bloques de reglas juntos para que se lean «por zonas».

A continuación, solo una pieza compacta que muestra el estilo: una zona, un grupo de constantes y reglas.

// junto a las otras constantes de ruta
private static final String[] DOCS = { "/swagger-ui/**", "/v3/api-docs/**" };

// dentro de authorizeHttpRequests(auth -> auth ...)

auth.requestMatchers(DOCS).authenticated();

auth.requestMatchers("/actuator/health").permitAll();
auth.requestMatchers("/actuator/info").authenticated();
auth.requestMatchers("/actuator/**").denyAll();

Sí, esto ya se parece a «extraer a constantes», y aquí es importante no exagerar. No queremos construir un «framework empresarial dentro de la seguridad». Pero una pequeña extracción de grupos de cadenas repetitivas no es un exceso arquitectónico, sino un cuidado por la legibilidad.

Si todavía no estás seguro de si extraer a métodos, al menos deja las constantes de arrays. Es un paso mínimo que hace que la configuración sea menos frágil: menos copia y pega, menos riesgo de escribir mal en un lugar y «abrir accidentalmente» un endpoint en otro.

Por ahora, todas estas reglas viven tranquilamente en una sola cadena común. La pregunta «¿se necesitan varios SecurityFilterChain para esto?» solo surge cuando las zonas de la aplicación empiezan a divergir tanto en política que una sola configuración deja de ser legible.

9. Errores típicos al trabajar con docs y Actuator

Error nº 1: «Es solo documentación, que sea pública».
Esta decisión se toma a menudo automáticamente, por inercia, sin preguntarse «¿quién necesita esto en producción?». La documentación pública a veces está justificada (en una API pública), pero mucho más a menudo Swagger es una herramienta interna cómoda. Si la dejas abierta «por si acaso», amplías la superficie de ataque y facilitas la vida a quienes no querías ayudar.

Error nº 2: Política diferente para /swagger-ui/** y /v3/api-docs/** sin motivo.
Los principiantes a veces cierran la interfaz de usuario pero dejan abierto el JSON de descripción de la API, o viceversa. Normalmente esto no ocurre por un plan ingenioso, sino porque «una ruta la recordé, la otra la olvidé». Si piensas en los docs como una sola zona, es más fácil mantenerlos en un solo array DOCS y aplicar una sola política, y hacer excepciones solo de forma consciente.

Error nº 3: Abrir /actuator/** para «comprobar la salud».
Esto es un clásico. Necesitas health, y parece lógico abrir todo /actuator/**, porque «quizás la monitorización pida algo más». Como resultado, publicas no solo la salud, sino también endpoints de diagnóstico potenciales que claramente no están diseñados para internet anónimo. El enfoque correcto es permitir puntualmente health, quizás info, y cerrar el resto con denyAll().

Error nº 4: Confundir el orden y cancelar accidentalmente tus propias restricciones.
Si pones "/actuator/**".denyAll() arriba, e intentas permitir "/actuator/health" más abajo, el permiso no funcionará. Si, por el contrario, pones "/**".permitAll() en alguna parte inferior, anulas todo. Las reglas en authorizeHttpRequests se leen de arriba a abajo, y debes tomarlo literalmente, sin esperar a que «Spring lo adivine».

Error nº 5: Esconder docs y actuator dentro del bloque general /api/** y luego sorprenderse.
La documentación y actuator suelen tener rutas diferentes, no bajo /api. Si mentalmente te has limitado con la idea de «mi API empieza con /api», simplemente olvidarás todo lo demás. Un buen hábito es mantener las reglas de docs y operaciones en un bloque separado en la configuración, para que no se diluyan entre los endpoints de negocio.

1
Tarea
Spring Security, nivel 18, lección 2
Bloqueada
Documentación cerrada y health público
Documentación cerrada y health público
1
Tarea
Spring Security, nivel 18, lección 2
Bloqueada
Allow-list para la zona de servicio del servicio de peliculas
Allow-list para la zona de servicio del servicio de peliculas
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION